Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to catch the original (inner) exception in C#?

Tags:

c#

exception

i'm calling a function that throws a custom exception:

GetLockOwnerInfo(...)

This function in turn is calling a function that throws an exception:

GetLockOwnerInfo(...)
   ExecuteReader(...)

This function in turn is calling a function that throws an exception:

GetLockOwnerInfo(...)
   ExecuteReader(...)
      ExecuteReader(...)

And so on:

GetLockOwnerInfo(...)
   ExecuteReader(...)
      ExecuteReader(...)
         ExecuteReaderClient(...)
             Fill(...)

One of these functions throws an SqlException, although that code has no idea what an SqlException is.

Higher levels wrap that SqlException into another BusinessRuleException in order to include some special properties and additional details, while including the "original" exception as InnerException:

catch (DbException ex)
{
    BusinessRuleExcpetion e = new BusinessRuleException(ex)
    ...
    throw e;
}

Higher levels wrap that BusinessRuleException into another LockerException in order to include some special properties and additional details, while including the "original" exception as InnerException:

catch (BusinessRuleException ex)
{
    LockerException e = new LockerException(ex)
    ...
    throw e;
}

The problem now is that i want to catch the origianl SqlException, to check for a particular error code.

But there's no way to "catch the inner exception":

try
{
   DoSomething();
}
catch (SqlException e)
{
   if (e.Number = 247) 
   {
      return "Someone";
   }
   else
      throw;
}

i thought about catching SqlException right when it's thrown, and copy various values to the re-thrown exception - but that code is not dependant on Sql. It is experiencing an SqlException, but it has no dependency on SqlException.

i thought about catching all exceptions:

try
{
   DoSomething(...);
}
catch (Exception e)
{
   SqlException ex = HuntAroundForAnSqlException(e);
   if (ex != null)
   {
      if (e.Number = 247) 
      {
          return "Someone";
      }
      else
         throw;
   }
   else
      throw;
}

But that's horrible code.

Given that .NET does not let you alter the Message of an Exception to include additional information, what is the intended mechanism to catch original exceptions?

like image 687
Ian Boyd Avatar asked Jan 09 '12 16:01

Ian Boyd


People also ask

How do you catch an inner exception?

The InnerException is a property of an exception. When there are series of exceptions, the most current exception can obtain the prior exception in the InnerException property. Let us say we have an exception inside a try block throwing an ArgumentException and the catch clause catches it and writes it to a file.

Where is the inner exception?

Inner exception will occur first and then the current exception will occur (if there is an exception) that is the reason why InnerException is checked against null . In order to retain inner exception we have to pass it as a parameter.

What does inner exception in C mean?

Exception. An object that describes the error that caused the current exception. The InnerException property returns the same value as was passed into the Exception(String, Exception) constructor, or null if the inner exception value was not supplied to the constructor. This property is read-only.

How do you catch all the exceptions?

Exception handling is used to handle the exceptions. We can use try catch block to protect the code. Catch block is used to catch all types of exception. The keyword “catch” is used to catch exceptions.


6 Answers

You need c# 6 / visual studio 2015 in order to do this using a predicate:

catch (ArgumentException e) when (e.ParamName == “…”)
{
}

Official C# Try/Catch Documentation

like image 194
Timothy Gonzalez Avatar answered Sep 23 '22 15:09

Timothy Gonzalez


I hate to have to tell you this, but you cannot catch an inner exception.

What you can do is inspect one.

I suggest you catch your high-level exception (I believe it was LockerException) and inspect the InnerException property of that exception. Check the type, and if it's not a SqlException, check the InnerException of that exception. Walk each one until you find a SqlException type, then get the data you need.

That said, I agree with dasblinkenlight that you should consider -- if possible -- a heavy refactor of your exception framework.

like image 37
Randolpho Avatar answered Sep 23 '22 15:09

Randolpho


Checking the error code of a wrapped exception is not a good practice, because it hurts encapsulation rather severely. Imagine at some point rewriting the logic to read from a non-SQL source, say, a web service. It would throw something other than SQLException under the same condition, and your outer code would have no way to detect it.

You should add code to the block catching SQLException to check for e.Number = 247 right then and there, and throw BusinessRuleException with some property that differentiates it from BusinessRuleException thrown in response to non-SQLException and SQLException with e.Number != 247 in some meaningful way. For example, if the magic number 247 means you've encountered a duplicate (a pure speculation on my part at this point), you could do something like this:

catch (SQLException e) {
    var toThrow = new BusinessRuleException(e);
    if (e.Number == 247) {
        toThrow.DuplicateDetected = true;
    }
    throw toThrow;
}

When you catch BusinessRuleException later, you can check its DuplicateDetected property, and act accordingly.

EDIT 1 (in response to the comment that the DB-reading code cannot check for SQLException)

You can also change your BusinessRuleException to check for SQLException in its constructor, like this:

public BusinessRuleException(Exception inner)
:   base(inner) {
    SetDuplicateDetectedFlag(inner);
}

public BusinessRuleException(string message, Exception inner)
:   base(message, inner) {
    SetDuplicateDetectedFlag(inner);
}

private void SetDuplicateDetectedFlag(Exception inner) {
    var innerSql = inner as SqlException;
    DuplicateDetected = innerSql != null && innerSql.Number == 247;
}

This is less desirable, because it breaks encapsulation, but at least it does it in a single place. If you need to examine other types of exceptions (e.g. because you've added a web service source), you could add it to the SetDuplicateDetectedFlag method, and everything would work again.

like image 32
Sergey Kalinichenko Avatar answered Sep 23 '22 15:09

Sergey Kalinichenko


Having an outer application layer care about the details of a wrapped exception is a code smell; the deeper the wrapping, the bigger the smell. The class which you now have wrapping the SqlException into a dbException is presumably designed to expose an SqlClient as a generic database interface. As such, that class should include a means of distinguishing different exceptional conditions. It may, for example, define a dbTimeoutWaitingForLockException and decide to throw it when it catches an SqlException and determines based upon its error code that there was a lock timeout. In vb.net, it might be cleaner to have a dbException type which exposes an ErrorCause enumeration, so one could then say Catch Ex as dbException When ex.Cause = dbErrorCauses.LockTimeout, but unfortunately exception filters are not usable in C#.

If one has a situation where the inner-class wrapper won't know enough about what it's doing to know how it should map exceptions, it may be helpful to have the inner-class method accept an exception-wrapping delegate which would take an exception the inner class has caught or would "like" to throw, and wrap it in a way appropriate to the outer class. Such an approach would likely be overkill in cases where the inner class is called directly from the outer class, but can be useful if there are intermediate classes involved.

like image 28
supercat Avatar answered Sep 24 '22 15:09

supercat


Good question and good answers!

I just want to supplement the answers already given with some further thoughts:

On one hand I agree with dasblinkenlight and the other users. If you catch one exception to rethrow an exception of a different type with the original exception set as the inner exception then you should do this for no other reason than to maintain the method's contract. (Accessing the SQL server is an implementation detail that the caller is not/must not/cannot be aware of, so it cannot anticipate that a SqlException (or DbException for that matter) will be thrown.)

Applying this technique however has some implications that one should be aware of:

  • You are concealing the root cause of the error. In your example you are reporting to the caller that a business rule was invalid(?), violated(?) etc., when in fact there was a problem accessing the DB (which would be immediately clear if the DbException were allowed to bubble up the call stack further).
  • You are concealing the location where the error originally occurred. The StackTrace property of the caught exception will point to a catch-block far away from the location the error originally occurred. This can make debugging notoriously difficult unless you take great care to log the stack traces of all the inner exceptions as well. (This is especially true once the software has been deployed into production and you have no means to attach a debugger...)

Given that .NET does not let you alter the Message of an Exception to include additional information, what is the intended mechanism to catch original exceptions?

It is true that .NET does not allow you to alter the Message of an Exception. It provides another mechanism however to supply additional information to an Exception via the Exception.Data dictionary. So if all you want to do is add additional data to an exception, then there is no reason to wrap the original exception and throw a new one. Instead just do:

public void DoStuff(String filename)
{
    try {
       // Some file I/O here...
    }
    catch (IOException ex) {

      // Add filename to the IOException
      ex.Data.Add("Filename", filename);

      // Send the exception along its way
      throw;
    }
}
like image 29
afrischke Avatar answered Sep 22 '22 15:09

afrischke


As other peeps say, you cannot catch an the InnerException. A function such as this could help you get the InnerException out of the tree though:

public static bool TryFindInnerException<T>(Exception top, out T foundException) where T : Exception
{
    if (top == null)
    {
        foundException = null;
        return false;
    }

    Console.WriteLine(top.GetType());
    if (typeof(T) == top.GetType())
    {
        foundException = (T)top;
        return true;
    }

    return TryFindInnerException<T>(top.InnerException, out foundException);
}
like image 39
Jeremy Avatar answered Sep 22 '22 15:09

Jeremy