何时在 C# 中重新引发异常?

Posted

技术标签:

【中文标题】何时在 C# 中重新引发异常?【英文标题】:When Rethrow a Exception in c#? 【发布时间】:2020-07-14 11:23:31 【问题描述】:

我已经阅读了为什么我们需要抛出异常并重新抛出它。但是我对何时重新抛出异常感到困惑?我在将 throw 放入 CalculationOperationNotSupportedException catch 时添加了一个示例,之后,我将 Stack Trace 与 Rethrowing 和不 Rethrowing 进行了比较。这是相同的 99%,但是当您重新抛出异常时,它只会添加位置。 当然,如果你准确地进行了两次堆栈跟踪。第 35 行是“抛出”位置编号,第 28 行是 int result = calculator.Calculate(number1, number2, operation); 我认为这里没有重新抛出的 Stack Trace 更好。你怎么看?

Stack Trace without rethrowing(throw) 我评论了它。

在 ConsoleCalculator.Calculator.Calculate(Int32 number1, Int32 number2,字符串操作)中 C:\Users\Behnam\Desktop\c-sharp-error-handling-exceptions\06\demos\after\03UsingExceptions\ConsoleCalculator\Calculator.cs:line 25 在 ConsoleCalculator.Program.Main(String[] args) 中 C:\Users\Behnam\Desktop\c-sharp-error-handling-exceptions\06\demos\after\03UsingExceptions\ConsoleCalculator\Program.cs:line 28

在 catch 中重新抛出堆栈跟踪(CalculationOperationNotSupportedException ex)

在 C:\Users\Behnam\Desktop\c-sharp-error-handling-exceptions\06\demos\after\03UsingExceptions\ConsoleCalculator\Calculator 中的 ConsoleCalculator.Calculator.Calculate(Int32 number1, Int32 number2, String operation) .cs:第 25 行 在 C:\Users\Behnam\Desktop\c-sharp-error-handling-exceptions\06\demos\after\03UsingExceptions\ConsoleCalculator\Program.cs:line 35 中的 ConsoleCalculator.Program.Main(String[] args) p>

public int Calculate(int number1, int number2, string operation)

    string nonNullOperation = 
        operation ?? throw new ArgumentNullException(nameof(operation));

    if (nonNullOperation == "/")
    
        try
        
            return Divide(number1, number2);
        
        catch (ArithmeticException ex)
        
            throw new CalculationException("An error occurred during division", ex);
        
    
    else
    
        throw new CalculationOperationNotSupportedException(operation);
    


static void Main(string[] args)

    var calculator = new Calculator();
    int number1=1;
    int number2=1;
    string operation = "+";
    try
    
        int result = calculator.Calculate(number1, number2, operation);
        DisplayResult(result);
    
    catch (CalculationOperationNotSupportedException ex)
    
        // Log.Error(ex);
        WriteLine(ex);
        throw;
    

【问题讨论】:

***.com/questions/2493779/… 【参考方案1】:

有两篇关于我经常链接的主题的文章。我认为它们是必读的。

https://blogs.msdn.microsoft.com/ericlippert/2008/09/10/vexing-exceptions/ https://www.codeproject.com/Articles/9538/Exception-Handling-Best-Practices-in-NET

基本上,如果您无法处理异常,则不应捕获它。但有时,您必须对有关异常的任何规则做出例外(不是双关语)。您可能必须更广泛地捕获,然后将“捕获和释放”应用于您获得的额外异常。例如,这是我在复制 TryParse 的尝试:

//Parse throws ArgumentNull, Format and Overflow Exceptions.
//And they only have Exception as base class in common, but identical handling code (output = 0 and return false).

bool TryParse(string input, out int output)
  try
    output = int.Parse(input);
  
  catch (Exception ex)
    if(ex is ArgumentNullException ||
      ex is FormatException ||
      ex is OverflowException)
      //these are the exceptions I am looking for. I will do my thing.
      output = 0;
      return false;
    
    else
      //Not the exceptions I expect. Best to just let them go on their way.
      throw;
    
  

  //I am pretty sure the Exception replaces the return value in exception case. 
  //So this one will only be returned without any Exceptions, expected or unexpected
  return true;

【讨论】:

【参考方案2】:

不,您对投掷和重投原理的理解不正确。

当您抛出异常时,您会丢失在新抛出异常之前发生的所有堆栈跟踪信息。问题是您将所有代码都放在同一个文件中,并且您在读取和正确比较每个堆栈跟踪中的信息时遇到问题。

构建以下代码,确保每个类都位于不同的 .cs 文件中。运行它并比较两个打印的堆栈跟踪,你会看到 throwing 丢失了信息:

//Foo.cs
public class Foo

    public void Crash() => throw new Exception();


//Blah.cs
public class Blah

    public void CrashAndThrow()
    
        var foo = new Foo();

        try 
         
            foo.Crash();
        
        catch (Exception ex)
        
            throw ex;
        
    

    public void CrashAndReThrow()
    
        var foo = new Foo();

        try 
         
            foo.Crash();
        
        catch
        
            throw;
        
    


//Program.cs
class Program

    static void Main(string[] args)
    
        var bla = new Blah();
        try
        
            bla.CrashAndThrow();
        
        catch (Exception ex)
        
            Console.WriteLine("Throw:");
            Console.WriteLine(ex.StackTrace);
            Console.WriteLine();
        

        try
        
            bla.CrashAndReThrow();
        
        catch (Exception ex)
        
            Console.WriteLine("Rethrow:");
            Console.WriteLine(ex.StackTrace);
        

        Console.ReadLine();
    

这个程序的输出是,在我的电脑里:

Throw:
   at SOStuff.Alpha.Blah.CrashAndThrow() in ...\SOStuff\Blah.cs:line 16
   at SOStuff.Program.Main(String[] args) in ...\SOStuff\Program.cs:line 13

Rethrow:
   at SOStuff.Alpha.Foo.Crash() in ...\SOStuff\Foo.cs:line 7
   at SOStuff.Alpha.Blah.CrashAndReThrow() in ...\SOStuff\Blah.cs:line 24
   at SOStuff.Program.Main(String[] args) in ...\SOStuff\Program.cs:line 24

如您所见,当您抛出时,Foo 中抛出的原始异常的所有信息都丢失了。

我应用的一般规则是:

    如果你不能处理,不要抓住它 如果您无法处理它,但您需要捕获它以进行清理、记录信息或其他任何事情,请重新抛出它。 如果您无法处理它,但又不允许异常中的敏感或不适当的信息到达消费者,则抛出一个具有足够信息的新信息,以便以后进行正确调试。 如果你能处理它,那就处理它。

99% 的情况下,我都会应用规则 #1。

【讨论】:

以上是关于何时在 C# 中重新引发异常?的主要内容,如果未能解决你的问题,请参考以下文章

编写高质量代码改善C#程序的157个建议——建议60:重新引发异常时使用Inner Exception

您可以在不同的线程上重新引发 .NET 异常吗?

在 C# 中出现异常时重新启动线程 [重复]

检测 DLL 何时引发异常

在 Ruby 中捕获异常后重新引发(相同的异常)

在 C# 中引发保留异常时会出现啥问题? [关闭]