126

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?

MS Srikkanth
  • 3,829
  • 3
  • 19
  • 33
  • 11
    It's useful if the `when` needs to access the exception itself – Tim Schmelter Jul 21 '16 at 07:37
  • 1
    But that is something we can do within the handler block itself as well right. Are there any benefits apart from a 'slightly more organized code'? – MS Srikkanth Jul 21 '16 at 07:40
  • 4
    But then you have already handled the exception which you don't want. What if you want to catch it somewhere else in this `try..catch...catch..catch..finally`? – Tim Schmelter Jul 21 '16 at 07:40
  • 4
    @user3493289: Following that argument, we don't need automatich type checks in exception handlers either: We can only allow `catch (Exception ex)`, check the type and `throw` otherwise. *Slightly more organized code* (aka avoiding code noise) is exactly why this feature exists. (This is actually true for a lot of features.) – Heinzi Jul 21 '16 at 07:42
  • 2
    @TimSchmelter Thanks. Post it as an answer and I will accept it. So the actual scenario would then be 'if the condition for handling depends on the exception', then use this feature/ – MS Srikkanth Jul 21 '16 at 07:42
  • @Tim you can always just re- throw; thr exception if you don't want to unwind.. – Mattias Åslund Jul 21 '16 at 07:44
  • @MattiasÅslund - I would presume that to be more expensive then just skipping the handler altogether – MS Srikkanth Jul 21 '16 at 07:44
  • @MattiasÅslund: True, but then it will only be handled by *outer* try catch blocks, not by a *later* catch in the *same* try ... catch ... catch block. – Heinzi Jul 21 '16 at 07:45
  • @Heinzi - But would it be a better organized code if you had multiple handlers for the same exception depending on different error conditions? I think that would make the error handling part stick out like a sore thumb. I would prefer sticking the different error condition check within the same method. That is why I bought Tim's answer. – MS Srikkanth Jul 21 '16 at 07:48
  • @MattiasÅslund: but you can't catch it in another `catch`-block in the same `try-catch` then. You also can't have the same `Exception` type in the same `try-catch`, you'll get a compiler error then. With `when` it is allowed. – Tim Schmelter Jul 21 '16 at 07:49
  • Great Q&A. Thanks all. Not often I learn something new these days :) – Mattias Åslund Jul 21 '16 at 07:49
  • @user3493289: I don't get your point yet: If you prefer `catch (A e) {...} catch (B e) {...}` over `catch (Exception e) {if (e is A) {...} ...}`, then why don't you prefer `catch (A e) when (e.c == 1) {...} catch (A e) when (e.c == 2) {...}` over `catch (A e) {if (e.c == 1) {...} ...}`? – Heinzi Jul 21 '16 at 07:52
  • @Heinzi - I prefer catch (A e) { Method1()} where Method1 is {if (e.c == 1) ... if (e.c == 2) ... this way the try-catch-finally block is minimal and the error handling doesn't affect the readability of the rest of your code... – MS Srikkanth Jul 21 '16 at 08:50
  • @user3493289: I see your point, but the same argument could be made for `catch (Exception e) {Method1()}` where Method1 is `if (e is A) ... if (e is B) ...`. But maybe we are just talking about different use cases: For top-level catch-all exception handlers, a separate method is quite useful. For specific-case exception handling within nested method calls, which happens rarely anyways, I like to be as specific as possible in my catch. – Heinzi Jul 21 '16 at 08:59
  • @Heinzi - they are 2 different use cases ... catch (A e) { if (e.c == 1) ... if (e.c == 2) ... } catch (B e) .... is fast as checking e.c during runtime doesn't affect performance. However comparing this to catch (Exception e) { if (e is A) .... if (e is B) .... } is not right....using 'is' operator can hurt your performance a lot in an already expensive exception handling routine....that is why saying 'if you prefer this you should prefer that as well' is like comparing apples and oranges – MS Srikkanth Jul 21 '16 at 09:04
  • @user3493289: I'd rather see this as two different kind of apples: The overhead of `is` is negligible compared to the overhead of throwing an exception, so we are talking about micro-optimization here. – Heinzi Jul 21 '16 at 09:09

3 Answers3

162

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.

Community
  • 1
  • 1
Heinzi
  • 167,459
  • 57
  • 363
  • 519
  • 2
    The second approach also does not allow to catch it in a different `catch`, does it? – Tim Schmelter Jul 21 '16 at 07:39
  • @TimSchmelter. True. You'd have to handle *all* COMExceptions in the same block. – Heinzi Jul 21 '16 at 07:44
  • 1
    While the `when` allows you to handle the same exception type multiple times. You should mention that as well since it's a crucial difference. Without `when` you'll get a compiler error. – Tim Schmelter Jul 21 '16 at 07:46
  • yes but the problem with this approach of error handling is that it makes your error handling code longer than it should be and arguably this affects readability more. But I guess that if you don't abuse it then it is good to have. So learnt about the side effect thing from the other answer and also a general use case where this feature can be used. Thanks all. – MS Srikkanth Jul 21 '16 at 07:53
  • @TimSchmelter: Thanks, added that to my answer. – Heinzi Jul 21 '16 at 08:06
  • 2
    @user3493289: that is often the case with ugly-ass code though. You think "I shouldn't be in this mess in the first place, redesign the code", and you also think "there could be a way to support this design elegantly, redesign the language". In this case there's a kind of threshold for how ugly you want your set of catch clauses to be, so something that makes certain situations less ugly lets you get more done within your threshold :-) – Steve Jessop Jul 21 '16 at 10:42
  • The alternative you propose isn't a like-for-like swap. See [Documentation for details](http://stackoverflow.com/documentation/c%23/24/c-sharp-6-0-features/46/exception-filters#t=201607211456270441687) – Joe Jul 21 '16 at 14:57
  • @Joe: Thanks. I've added a link to an example which shows in which way the stack trace is affected. – Heinzi Jul 21 '16 at 19:13
48

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

Note this refers to the stack itself, not the stack trace, the latter of which can preserved by simply using throw; instead of throw ex;, but will still cause the stack to unwind.

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.

Eli Arbel
  • 22,391
  • 3
  • 45
  • 71
  • Yes. throw; preserves the original information on where the execption was thrown. This seems to be wrong. – MS Srikkanth Jul 21 '16 at 07:50
  • 4
    @Heinzi See [my answer in another thread](http://stackoverflow.com/a/11229760/1336654) where you can see that `throw;` changes the stack trace a little and `throw ex;` changes it a lot. – Jeppe Stig Nielsen Jul 21 '16 at 08:23
  • 1
    Using `throw` does disturb the stack trace slightly. Line numbers are different when using `throw` as opposed to `when`. – Mike Zboray Jul 21 '16 at 08:23
  • Proper semantics are often best achieved by having code within a `finally` block that "knows" whether it's being called in normal program flow or as part of exception handling. This is especially important if an exception occurs during the `finally` code itself. Using a method in `when` to capture the pending exception will make it possible for code to emulate a `finally(Exception ex) {...}` structure which IMHO should have been part of the language in the first place. I don't consider emulation of features that should have existed as being particularly "abusive". – supercat Jul 21 '16 at 15:13
  • @supercat Especially when the underlying IL *does* have `fault` which is exactly the feature you're referring to. I can't find a good MSDN reference, but [this one](http://weblogs.asp.net/kennykerr/introduction-to-msil-part-5-exception-handling) will do. – Mark Hurd Jul 27 '16 at 00:55
  • @MarkHurd: Unfortunately, I don't think `fault` makes the pending exception available to the code; an exception filter can be used to capture the pending exception, and a fault block can be used to augment that with a flag to say whether the exception is *still* pending when the `finally` block is run, but that shouldn't need to dictate language design. IMHO the cleanest way for the language to expose things would be via parameter in the `finally` block, and a compiler should be able to generate the IL necessary to achieve that using filters and `fault`. – supercat Jul 27 '16 at 01:37
8

When an exception is thrown, the first pass of exception handling identifies where the exception will get caught before unwinding the stack; if/when the "catch" location is identified, all "finally" blocks are run (note that if an exception escapes a "finally" block, processing of the earlier exception may be abandoned). Once that happens, code will resume execution at the "catch".

If there is a breakpoint within a function that's evaluated as part of a "when", that breakpoint will suspend execution before any stack unwinding occurs; by contrast, a breakpoint at a "catch" will only suspend execution after all finally handlers have run.

Finally, if lines 23 and 27 of foo call bar, and the call on line 23 throws an exception which is caught within foo and rethrown on line 57, then the stack trace will suggest that the exception occurred while calling bar from line 57 [location of the rethrow], destroying any information about whether the exception occurred in the line-23 or line-27 call. Using when to avoid catching an exception in the first place avoids such disturbance.

BTW, a useful pattern which is annoyingly awkward in both C# and VB.NET is to use a function call within a when clause to set a variable which can be used within a finally clause to determine whether the function completed normally, to handle cases where a function has no hope of "resolving" any exception that occurs but must nonetheless take action based upon it. For example, if an exception is thrown within a factory method which is supposed to return an object that encapsulates resources, any resources that were acquired will need to be released, but the underlying exception should percolate up to the caller. The cleanest way to handle that semantically (though not syntactically) is to have a finally block check whether an exception occurred and, if so, release all resources acquired on behalf of the object that is no longer going to be returned. Since cleanup code has no hope of resolving whatever condition caused the exception, it really shouldn't catch it, but merely needs to know what happened. Calling a function like:

bool CopySecondArgumentToFirstAndReturnFalse<T>(ref T first, T second)
{
  first = second;
  return false;
}

within a when clause will make it possible for the factory function to know that something happened.

supercat
  • 77,689
  • 9
  • 166
  • 211