I came across this new feature in C# which allows a catch handler to execute when a specific condition is met.
int i = 0; try { throw new ArgumentNullException(nameof(i)); } catch (ArgumentNullException e) when (i == 1) { Console.WriteLine("Caught Argument Null Exception"); }
I am trying to understand when this may ever be useful.
One scenario could be something like this:
try { DatabaseUpdate() } catch (SQLException e) when (driver == "MySQL") { //MySQL specific error handling and wrapping up the exception } catch (SQLException e) when (driver == "Oracle") { //Oracle specific error handling and wrapping up of exception } ..
but this is again something that I can do within the same handler and delegate to different methods depending on the type of the driver. Does this make the code easier to understand? Arguably no.
Another scenario that I can think of is something like:
try { SomeOperation(); } catch(SomeException e) when (Condition == true) { //some specific error handling that this layer can handle } catch (Exception e) //catchall { throw; }
Again this is something that I can do like:
try { SomeOperation(); } catch(SomeException e) { if (condition == true) { //some specific error handling that this layer can handle } else throw; }
Does using the 'catch, when' feature make exception handling faster because the handler is skipped as such and the stack unwinding can happen much earlier as when compared to handling the specific use cases within the handler? Are there any specific use cases that fit this feature better which people can then adopt as a good practice?
You should catch the exception when you are in the method that knows what to do. For example, forget about how it actually works for the moment, let's say you are writing a library for opening and reading files. Here, the programmer knows what to do, so they catch the exception and handle it.
Place any code statements that might raise or throw an exception in a try block, and place statements used to handle the exception or exceptions in one or more catch blocks below the try block. Each catch block includes the exception type and can contain additional statements needed to handle that exception type.
When exception happens in the method, a special method exception table is checked, it contains records for each catch block: exception type, start instruction and end instruction. If the order of exception is incorrect, some catch block would be unreachable.
Order of exceptions If you have multiple catch blocks for a single try and if the exceptions classes of them belong to the same hierarchy, You need to make sure that the catch block that catches the exception class of higher-level is at last at the last in the order of catch blocks.
Catch blocks already allow you to filter on the type of the exception:
catch (SomeSpecificExceptionType e) {...}
The when
clause allows you to extend this filter to generic expressions.
Thus, you use the when
clause for cases where the type of the exception is not distinct enough to determine whether the exception should be handled here or not.
A common use case are exception types which are actually a wrapper for multiple, different kinds of errors.
Here's a case that I've actually used (in VB, which already has this feature for quite some time):
try { SomeLegacyComOperation(); } catch (COMException e) when (e.ErrorCode == 0x1234) { // Handle the *specific* error I was expecting. }
Same for SqlException
, which also has an ErrorCode
property. The alternative would be something like that:
try { SomeLegacyComOperation(); } catch (COMException e) { if (e.ErrorCode == 0x1234) { // Handle error } else { throw; } }
which is arguably less elegant and slightly breaks the stack trace.
In addition, you can mention the same type of exception twice in the same try-catch-block:
try { SomeLegacyComOperation(); } catch (COMException e) when (e.ErrorCode == 0x1234) { ... } catch (COMException e) when (e.ErrorCode == 0x5678) { ... }
which would not be possible without the when
condition.
From Roslyn's wiki (emphasis mine):
Exception filters are preferable to catching and rethrowing because they leave the stack unharmed. If the exception later causes the stack to be dumped, you can see where it originally came from, rather than just the last place it was rethrown.
It is also a common and accepted form of “abuse” to use exception filters for side effects; e.g. logging. They can inspect an exception “flying by” without intercepting its course. In those cases, the filter will often be a call to a false-returning helper function which executes the side effects:
private static bool Log(Exception e) { /* log it */ ; return false; } … try { … } catch (Exception e) when (Log(e)) { }
The first point is worth demonstrating.
static class Program { static void Main(string[] args) { A(1); } private static void A(int i) { try { B(i + 1); } catch (Exception ex) { if (ex.Message != "!") Console.WriteLine(ex); else throw; } } private static void B(int i) { throw new Exception("!"); } }
If we run this in WinDbg until the exception is hit, and print the stack using !clrstack -i -a
we'll see the just the frame of A
:
003eef10 00a7050d [DEFAULT] Void App.Program.A(I4) PARAMETERS: + int i = 1 LOCALS: + System.Exception ex @ 0x23e3178 + (Error 0x80004005 retrieving local variable 'local_1')
However, if we change the program to use when
:
catch (Exception ex) when (ex.Message != "!") { Console.WriteLine(ex); }
We'll see the stack also contains B
's frame:
001af2b4 01fb05aa [DEFAULT] Void App.Program.B(I4) PARAMETERS: + int i = 2 LOCALS: (none) 001af2c8 01fb04c1 [DEFAULT] Void App.Program.A(I4) PARAMETERS: + int i = 1 LOCALS: + System.Exception ex @ 0x2213178 + (Error 0x80004005 retrieving local variable 'local_1')
That information can be very useful when debugging crash dumps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With