Rethrowing Exceptions in C#

A colleague recently mentioned to me that he thought that re-throwing an exception with ‘throw ex’ was a Bad Thing… and as I was not aware of this I did some comparisons of different options to at least try to understand what I was losing out on by using ‘throw ex’.

First, I should specify that this was in a wrapper method around a framework function that we did not have control of. Therefore, from a conceptual point of view, I don’t think that I mind missing out on trace data from ‘inside’ the exception. I don’t have access to that code, so the call-stack of functions that failed inside the 3rd party framework is pretty-much meaningless to me.

Anyway, this was a wrapper around the Gentle Framework ‘Retrieve’ function; which will throw an exception if the key that you pass it is not found in the database. I won’t get side-tracked with why it is ‘unfortunate’ we often find ourselves attempting to retrieve records that don’t exist… or ‘may not’ exist (I’ll leave that to your imagination). So, my basic routine looked a little like this:

public person RetrieveSafe(long personId)
{
    person p = null;
    try
    {
        p = person.Retrieve(personId);
    }
    catch (Gentle.Common.GentleException ex)
    {
        if (ex.Message == "Some value identifying it as a key-not-found")
            return null;
        throw ex;
    }
    return p;
}

Now, I’m going to cheat a bit and comment out the two lines that test for the particular error I want to return null for, so that I can easily get an exception by requesting a record I know does not exist!

With the ‘throw ex’ as shown, I get the following output from ex.ToString():

Error: UnexpectedRowCountUnclassified
Gentle.Common.GentleException: The number of returned rows 0 did not match the expected count of 1.
If concurrency control is enabled this may indicate that the record was updated or deleted by another process.
  at XYZ.person.RetrieveSafe(Int64 personId) in C:\\XYZ\\person.cs:line 59
  at XYZ.buttonNewLoanWorkflow_Click(Object sender, EventArgs e) in C:\\XYZ\\Utilities.cs:line 397

If I change the ‘throw ex’ to ‘throw’, the output in ex.ToString() is:

Error: UnexpectedRowCountUnclassified
Gentle.Common.GentleException: The number of returned rows 0 did not match the expected count of 1.
If concurrency control is enabled this may indicate that the record was updated or deleted by another process.
  at Gentle.Common.Check.FailWith(Severity severity, Error error, Exception e, String msg)
  at Gentle.Common.Check.Fail(Exception e, Error error, Object[] args)
  at Gentle.Common.Check.Verify(Boolean condition, Error error, Object[] args)
  at Gentle.Framework.ObjectFactory.GetInstance(Type type, SqlResult sr, Key key)
  at Gentle.Framework.PersistenceBroker.RetrieveInstance(Type type, Key key, IDbConnection conn, IDbTransaction tr)
  at Gentle.Framework.Broker.RetrieveInstance(Type type, Key key)
  at XYZ.person.Retrieve(Int64 id) in C:\\XYZ\\Gentle\\person.cs:line 1128
  at XYZ.person.RetrieveSafe(Int64 personId) in C:\\XYZ\\Objects\\person.cs:line 59
at XYZ.buttonNewLoanWorkflow_Click(Object sender, EventArgs e) in C:\\XYZ\\Utilities.cs:line 397

And finally, if I change it to ‘throw new Exception(“MY HELPFUL MESSAGE”, ex)’:

System.Exception: MY HELPFUL MESSAGE ---> Error: UnexpectedRowCountUnclassified
Gentle.Common.GentleException: The number of returned rows 0 did not match the expected count of 1.
If concurrency control is enabled this may indicate that the record was updated or deleted by another process.
  at Gentle.Common.Check.FailWith(Severity severity, Error error, Exception e, String msg)
  at Gentle.Common.Check.Fail(Exception e, Error error, Object[] args)
  at Gentle.Common.Check.Verify(Boolean condition, Error error, Object[] args)
  at Gentle.Framework.ObjectFactory.GetInstance(Type type, SqlResult sr, Key key)
  at Gentle.Framework.PersistenceBroker.RetrieveInstance(Type type, Key key, IDbConnection conn, IDbTransaction tr)
  at Gentle.Framework.Broker.RetrieveInstance(Type type, Key key)
  at XYZ.person.Retrieve(Int64 id) in C:\\XYZ\\Gentle\\person.cs:line 1128
  at XYZ.person.RetrieveSafe(Int64 personId) in C:\\XYZ\\Objects\\person.cs:line 52
  --- End of inner exception stack trace ---
  at LoanBook2.person.RetrieveSafe(Int64 personId) in C:\\XYZ\\Objects\\person.cs:line 59
  at XYZ.buttonXyz_Click(Object sender, EventArgs e) in C:\\XYZ\\Utilities.cs:line 397

It seems to me that the quantity of irrelevant information in the latter two forms far exceeds the useful information, again reminding ourselves that I have no desire or capability to understand the control flow within the Gentle Framework.

This is not to discount what other people say; that the ‘throw ex’ option is the worst in terms of performance… nor to dispute that the third option would be the ideal location to record the personId that was not retrieved. The summary for me is that I am not terribly unhappy with the code as it stands, considering a whole raft of factors that make the application I work with ‘horrible code’.

Update: There has been a spirited bit of back-and-forth on StackOverflow on this subject. The general consensus, contrary to my POV outlined above, is that a simple ‘throw’ is better. I’m still not sure! This link is highlighted there are the ultimate resource for understanding exception handling, but I’ll admit that I have not read it yet.

Update 2: Oops. Ok, I just realised that ‘my way’ does not show the line number inside the try block that caused the Exception. It’s not a massive problem in this particular instance, because there is only one line… but I finally grok what I was missing in terms of ‘useful information’.