danielrbradley danielrbradley - 1 month ago 8
C# Question

Why doesn't .NET exceptions work against an interface rather than a base class?

The .Net framework try-catch implementation only allows you to catch types which inherit off the base class "System.Exception". Why could this not have been an interface such as "System.IException"?

Use case



We use a custom base class in each API that inherits off System.Exception. This is only thrown once an exception has been logged and therefore we can easily avoid re-logging by something like:

try
{
// Do something.
}
catch (LoggedException)
{
// Already logged so just rethrow.
throw;
}
catch (Exception ex)
{
// TODO: Log exception.
throw new LoggedException("Failed doing something.", ex);
}


This is great until you want a custom exception that inherits off another system exception type such as System.FormatException

The only way to now handle both of these is to have two custom base types and duplicate every catch statement.

Refactoring



If the .net framework just simply looked for something such as System.IException then you could simply have a custom exception interface such as CompanyName.ILoggedException, inheriting System.IException which all your custom exception types implement. Therefore your new catch code would look something like:

try
{
// Do something.
}
catch (ILoggedException)
{
// Already logged so just rethrow.
throw;
}
catch (IException ex)
{
// TODO: Log exception.
throw new CustomException("Failed doing something.", ex);
}


Is there a practical reason the framework is implemented like this? Or would this be something to request in a future version of the .Net framework?

Answer

One possibly neater workaround that hasn't been mentioned yet is to use extension methods. By harnessing the Exception.Data field, you could neatly discover from a single catch block whether the current exception has been logged yet, and take action as required. This would allow you to construct numerous different company specific exceptions (which are implicitly already logged).

Extension methods required:

private const string ExceptionLoggedKey = "IsExceptionLogged";

public static bool IsLogged(this Exception exception)
{
    if (exception.Data.Contains(ExceptionLoggedKey))
    {
        return (bool)exception.Data[ExceptionLoggedKey];
    }
    return false;
}

public static void SetLogged(this Exception exception)
{
    exception.Data.Add(ExceptionLoggedKey, true);
}

Company exceptions take the following format, setting the IsLogged flag in the constructor:

public class CompanysLoggedException : InvalidOperationException  //Could be any Exception
{
    public CompanysLoggedException()
    {
        this.SetLogged();
    }
}

try/catch usage:

try
{
    throw new ArgumentException("There aren't any arguments.");
}
catch (Exception ex)
{
    if (ex.IsLogged())
        //Nothing additional to do - simply throw the exception
        throw;
    else
        //TODO Log the exception
        throw new CompanysLoggedException();
}

I'd agree that this definitely isn't as neat as the ability to match an exception based on an implemented interface, but I think the pattern is quite concise and readable. Having to remember to add the call to SetLogged() to each new company exception defined is a bit of a flaw though.