ChatGPT解决这个技术问题 Extra ChatGPT

Is there a difference between "throw" and "throw ex"?

There are some posts that asks what the difference between those two are already. (why do I have to even mention this...)

But my question is different in a way that I am calling "throw ex" in another error god-like handling method.

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.
    }
}

If try & catch were used in the Main, then I would use throw; to rethrow the error. But in the above simplied code, all exceptions go through HandleException

Does throw ex; has the same effect as calling throw when called inside HandleException?

There is a difference, it has to do with whether or how the stack trace appears in the exception, but I don't remember which is which right now so I won't list this an answer.
@Joel: Thanks. I guess using HandleError exception is a bad idea. I just wanted to refactor some error handling code.
The third way is to wrap in a new exception and rethrow timwise.blogspot.co.uk/2014/05/…
Watch this youtube video throw vs throw ex youtube.com/watch?v=WGwOd6yMja4 When you see things it becomes more clear.

i
iliketocode

Yes, there is a difference.

throw ex resets the stack trace (so your errors would appear to originate from HandleException)

throw doesn't - the original offender would be preserved. 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; } }


@Shaul; no, it isn't. I've given details in a comment to your post.
@Marc Gravell - my apologies, you were right. Sorry about the downvote; it's too late for me to undo... :(
@Marc : It seems that throw preserves the original offender ONLY if the throw isn't in the method in which the initial exception was thrown (see this question : stackoverflow.com/questions/5152265/… )
No, you're right. In that case throw; and throw ex; throw the same object, but its stack trace is modified in different way, as you explain in your answer.
@ScottDorman Looks like your link isn't being forwarded correctly after a blog migration. Looks like it now lives here. Edit: Hey, wait, that's your blog! Fix your own links! ;^D
N
Nerdroid

(I posted earlier, and @Marc Gravell has corrected me)

Here's a demonstration of the difference:

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
}

and here is the output:

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

You can see that in Exception 1, the stack trace goes back to the DivByZero() method, whereas in Exception 2 it does not.

Take note, though, that the line number shown in ThrowException1() and ThrowException2() is the line number of the throw statement, not the line number of the call to DivByZero(), which probably makes sense now that I think about it a bit...

Output in Release mode

Exception 1:

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

Exception 2:

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

Is it maintains the original stackTrace in debug mode only?


Its because the compiler's optimization process inlines short methods such as DevideByZero, so the stack trace IS the same. maybe you should post this as a question on it`s own
S
Shivprasad Koirala

Throw preserves the stack trace. So lets say Source1 throws Error1 , its caught by Source2 and Source2 says throw then Source1 Error + Source2 Error will be available in the stack trace.

Throw ex does not preserve the stack trace. So all errors of Source1 will be wiped out and only Source2 error will sent to the client.

Sometimes just reading things are not clear , would suggest to watch this video demo to get more clarity , Throw vs Throw ex in C#.

https://i.stack.imgur.com/6DMcn.png


Very nice diagram there :)
nice explanation.
N
Nerdroid

The other answers are entirely correct, but this answer provides some extra detalis, I think.

Consider this example:

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);
  }
}

If you uncomment the throw arithExc; line, your output is:

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.

Certainly, you have lost information about where that exception happened. If instead you use the throw; line, this is what you get:

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.

This is a lot better, because now you see that it was the Program.Div method that caused you problems. But it's still hard to see if this problem comes from line 35 or line 37 in the try block.

If you use the third alternative, wrapping in an outer exception, you lose no information:

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

In particular you can see that it's line 35 that leads to the problem. However, this requires people to search the InnerException, and it feels somewhat indirect to use inner exceptions in simple cases.

In this blog post they preserve the line number (line of the try block) by calling (through reflection) the internal intance method InternalPreserveStackTrace() on the Exception object. But it's not nice to use reflection like that (the .NET Framework might change their internal members some day without warning).


N
Nerdroid

let's understand the difference between throw and throw ex. I heard that in many .net interviews this common asked is being asked.

Just to give an overview of these two terms, throw and throw ex are both used to understand where the exception has occurred. Throw ex rewrites the stack trace of exception irrespective where actually has been thrown.

Let's understand with an example.

Let's understand first 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();
}

output of the above is below.

shows complete hierarchy and method name where actually the exception has thrown.. it is M2 -> M2. along with line numbers

https://i.stack.imgur.com/wUu6g.png

Secondly.. lets understand by throw ex. Just replace throw with throw ex in M2 method catch block. as below.

https://i.stack.imgur.com/GlLqc.png

output of throw ex code is as below..

https://i.stack.imgur.com/V575m.png

You can see the difference in the output.. throw ex just ignores all the previous hierarchy and resets stack trace with line/method where throw ex is written.


l
liviriniu

When you do throw ex, that thrown exception becomes the "original" one. So all previous stack trace will not be there.

If you do throw, the exception just goes down the line and you'll get the full stack trace.


L
Lucero

No, this will cause the exception to have a different stack trace. Only using a throw without any exception object in the catch handler will leave the stack trace unchanged.

You may want to return a boolean from HandleException whether the exception shall be rethrown or not.


P
Pang

Microsoft Docs stands for:

Once an exception is thrown, part of the information it carries is the stack trace. The stack trace is a list of the method call hierarchy that starts with the method that throws the exception and ends with the method that catches the exception. If an exception is re-thrown by specifying the exception in the throw statement, the stack trace is restarted at the current method and the list of method calls between the original method that threw the exception and the current method is lost. To keep the original stack trace information with the exception, use the throw statement without specifying the exception.

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


N
Nerdroid

Look at here: 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;
}

It preserve the Stack information with Exception

This is called as "Rethrow"

If want to throw new exception,

throw new ApplicationException("operation failed!");

Throw Ex:

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

It Won't Send Stack information with Exception

This is called as "Breaking the Stack"

If want to throw new exception,

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

M
Maksud

It's better to use throw instead of throw ex.

throw ex reset the original stack trace and can't be found the previous stack trace.

If we use throw, we will get a full stack trace.


N
Nerdroid

To give you a different perspective on this, using throw is particularly useful if you're providing an API to a client and you want to provide verbose stack trace information for your internal library. By using throw here, I'd get the stack trace in this case of the System.IO.File library for File.Delete. If I use throw ex, then that information will not be passed to my handler.

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

To expand on Lucero's answer, here's how you can accomplish the intent of the original code without losing the original stack trace.

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

if all Line 1 ,2 and 3 are commented - Output - inner ex if all Line 2 and 3 are commented - Output - inner ex System.DevideByZeroException: {"Attempted to divide by zero."}--------- if all Line 1 and 2 are commented - Output - inner ex System.Exception: devide by 0 ---- if all Line 1 and 3 are commented - Output - inner ex System.DevideByZeroException: {"Attempted to divide by zero."}---------

and StackTrace will be reset in case of throw ex;