Pluralsight Logo
Author avatar

Chris Behrens

Author badge Author

Catching Different Exception Types

Chris Behrens

Author BadgeAuthor
  • Oct 29, 2018
  • 8 Min read
  • 10 Views
  • Oct 29, 2018
  • 8 Min read
  • 10 Views
C#

The Problem This Solves

Different exception types represent different things going wrong. When different things break, we may want to use another process to get the system back to the state that we want – we can do this by handling different exception types in alternate catch blocks. When we see a try-catch block like this:

1
2
3
4
5
6
7
8
try
{
    // perform actions that may throw an exception
}
catch
{
    // perform a non-specific action to recover
}

The outcome of this catch block is effectively “I don’t care what went wrong or why or what the details were – whatever happened, here’s how we’ll recover from it”. And that’s fine, as far as it goes, but different exceptions can have different recovery scenarios. Recovering from trying to fit a value too large to fit in a data type should be very different from recovering from not being able to get a file handle. And even if our handling is going to be mostly generic, wouldn’t it be nice to know WHICH file handle you couldn’t get, or what the value was that we couldn’t put into the data type? Even if it’s only in the debugger, it’s good to have this finer level of detail about what went wrong. We’ll look at how to make this happen.

Our Scenario

Our price updating code from the first objective is humming along nicely – with some diagnostics, we have determined that it fails intermittently three or four times per week. But in a conversation with our network guy, he says “That’s weird…we made some upgrades, and there’s really only ONE time last week that this could have been network trouble. Are you sure there’s not something else going on here?” After some tricky troubleshooting you find the problem – sometimes, rather than returning the price as 30.84, the service will return the price as $30.84. Our code currently looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var priceClient = new PriceClient();

try
{
    priceClient.Connect();

    var updatedPrice = priceClient.GetPrice();

    return updatedPrice;
}
catch
{
    return null;
}

Our catch block is catching two types of exceptions. The first type is the one we expected - the ConnectionFailedException - when the network is misbehaving. The second is an InvalidCastException -our priceClient is failing internally when it tries to change $30.84 into a decimal type. The problem is just the dollar sign at the beginning – if we could trim it off, we could update the price more often and have a better system. But our catch block catches EVERY type of exception and doesn’t give us the flexibility to handle different problems individualy.

Typed-exception Catch Blocks

We want to continue to return null if we encounter a ConnectionFailedException, but we want to trim the dollar sign off and return the price if we get an InvalidCastException. To make this happen, we’ll add a new catch block above our generic catch block:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
try
{
    priceClient.Connect();

    var updatedPrice = priceClient.GetPrice();

    return updatedPrice;
}
catch(InvalidCastException)
{
    // handle the bad price
}
catch
{
    return null;
}

With this flow, if we get our ConnectionFailedException, it will fall past our InvalidCastException block and return null, as we want it to. Our priceClient object has a ReturnedValue property that contains the raw string for the price – all we have to do is trim off the first character with the Substring function and parse it into a decimal, like this:

1
2
3
4
catch(InvalidCastException)
{
return decimal.Parse(priceClient.ReturnedValue.Substring(1);
}

With this final structure:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static decimal? GetCurrentPrice()
{
    var priceClient = new PriceClient();

    try
    {
        priceClient.Connect();

        var updatedPrice = priceClient.GetPrice();

        return updatedPrice;
    }
        catch(InvalidCastException)
    {
        return decimal.Parse(priceClient.ReturnedValue.Substring(1));
    }
    catch
    {
        return null;
    }
}

Our code handles the connection failure by returning null and handles the casting problem by repairing the data and performing a proper cast, allowing our system to update more often.

Exception Variables

You’re talking to your IT buddy again - he explains that the service you’re calling is actually a series of load-balanced servers. So, each call you make may hit a different physical machine. He’s convinced that the network problems are happening with a particular server in the pool and, if we could tell the vendor which server was failing, they could pull it out and fix it. After talking with the vendor, they tell us that the ConnectionFailedException has a ServerName property on it. If we could capture that value in the logs, that would do the trick - but we’re not capturing the exception detail with our code, yet. To capture that detail, we need to do two things:

  1. Add a specific exception block for the ConnectionFailedException type
  2. Add an exception variable:
1
2
3
4
5
6
7
8
catch(ConnectionFailedException connectionFailed)
{
    var serverName = connectionFailed.ServerName;

    // log servername details here

    return null;
}

With the exception variable <connectionFailed> defined in the catch block, we can access its ServerName property in the scope of the catch block. We capture that on the next line, log the details, and return null.

Top-level Error Handlers

In talking with your IT buddy, he points out, “You know, if you’d only been catching that connection failure rather than everything, the service would have kept crashing, and we’d have found it much faster. But I guess that only would have happened if we’d noticed the service crashing.” You consider this and update your code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static decimal? GetCurrentPrice()
{
    var priceClient = new PriceClient();

    try
    {
        priceClient.Connect();

        var updatedPrice = priceClient.GetPrice();

        return updatedPrice;
    }
    catch(InvalidCastException)
    {
        return decimal.Parse(priceClient.ReturnedValue.Substring(1));
    }
    catch(ConnectionFailedException connectionFailed)
    {
        var serverName = connectionFailed.ServerName;

        // log servername details here

        return null;
    }
    catch
    {
        return null;
    }
}

Without the generic catch block at the end, if there’s a new exception that you haven’t accounted for, an exception that will rise up the stack and crash the service. What if you wrapped the entire service in a generic catch block that, when an unexpected exception occurred, sent you an email? Or a Slack notification, or whatever. When this happened, you would now know that there was a scenario you’d failed to account for and you could go ADD that scenario to the code in the right place. You need to add this top-level error handler and remove the catch block at the end:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static decimal? GetCurrentPrice()
{
    var priceClient = new PriceClient();

    try
    {
        priceClient.Connect();

        var updatedPrice = priceClient.GetPrice();

        return updatedPrice;
    }
    catch(InvalidCastException)
    {
        return decimal.Parse(priceClient.ReturnedValue.Substring(1));
    }
    catch(ConnectionFailedException connectionFailed)
    {
        var serverName = connectionFailed.ServerName;

        // log servername details here

        return null;
    }
}

Now, if an ArgumentException, an OutofMemoryException, or a NeverThoughtThisCouldHappenException occurs, it will rise up the stack and notify you so that you can create a system that takes the new scenario into account.

0