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

10,588

Solution 1

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.

Solution 2

As you probably know that in base class case we only have single inheritance but in case of interface a class can implement many interfaces so if you have something like:

class MyException : IExceptionB, IExceptionA
{
}

try
{
 throw new MyException();
}
catch(IExceptionB b){}
catch(IExceptionA b){}

Now this creates ambiguity regarding how to decide which catch handler to call as the exception implements both interface i.e in terms of hierarchy both are at same level unlike the base class where there won't be two class at same level.

The code is hypothetical showing the problem in case Interface based catch was allowed

Solution 3

I don't really know the reason, but I assume that it's about performance. In case of an exception, each catch block has to check if it machtes the exception. If you allow only types, that's quite simple in case of .Net because you have only single inheritance. Inheritance trees of implemented interfaces can become much more complex.

Solution 4

C#6 introduced exception filtering so what you ask for is now possible in C# (it has long been possible in VB.Net). We can now use the when keyword.

Here is your code refactored to use the new syntax:

try
{
      ...
}
catch (Exception ex) when (!(ex is ILoggedException))
{
    // TODO: Log exception.
    throw new Exception("Failed doing something.", ex);
}

Note that we no longer need the first catch block as it was essentially just a filter and all it did was throw.

Class and interface definitions:

public interface ILoggedException { }

public class CustomLoggedException : Exception, ILoggedException { ... }

Solution 5

It is possible in VB to catch interfaces by using something like:

  ' Doesn't require external function, but will will require typecast
  ' if it needs to actually use Ex as IMyExceptionInterface
  Catch Ex As Exception When TypeOf(Ex) Is IMyExceptionInterface
  ' Alternate form: requires pre-declared variable and a function:
  ' Function TryCastIntoSecondParam(Of TSource As Class, TDest As Class) _
  '                                (ByVal Thing As TSource,  ByRef Dest As TDest)
  '   Dim Result As TDest
  '   Result = TryCast(Thing, TDest)
  '   Dest = Result
  '   Return Dest IsNot Nothing
  ' End Function
  Catch Ex As Exception When TryCastIntoSecondParam(Ex, myIMyException)

If the VB or C# compiler implementers wanted to do so, they could allow one to use a syntax

  Catch Ex As IMyExceptionInterface  ' vb
  catch IExceptionInterface ex       ' C#

and implement it with the above code. Even without compiler support, vb users can get the correct semantics with the above code. In C#, it would be necessary to catch an exception, test whether it's the desired type, and rethrow if it isn't; the semantics of that are different from those of using a filter to avoid catching the exception in the first place. Note that for the C# compiler to implement the above constructs, it would have to use filter blocks, but it would not have to expose all the power of filter blocks to programmers--something which the C# implementors have deliberately refused to do.

All that having been said, I suspect the answer to the original question might be "the designers couldn't think of any good use cases for such a thing", or it might be "Interfaces require more complex type resolution than do classes, creating the possibility that the mere act of deciding whether to catch an exception could fail with an exception of its own."

Actually, I happen to dislike the use of class types as a means of deciding what exceptions to catch, since the question of whether or not to catch an exception is often largely orthogonal to the question of what precisely happened to cause it. If an attempt to load a document fails, I'm not nearly as interested in the question of whether some argument was out of range or some index was out of range, as I am in the question of whether I can safely recover from the attempt by pretending I'd never made it. What's really needed is a "severity" measure for exceptions which can be increased or decreased as the exception bubbles up the call chain. Such a thing might be somewhat practical in vb.net (which has exception filters) but probably not in C# (which doesn't), but would be limited in any case by the lack of any support within the built-in exceptions.

Edit/Addendum

It is possible to use exception filters within a C# project if one uses a DLL written in vb to implement a try/filter/catch/finally wrapper which calls some delegates. Unfortunately, using such a DLL would require some tradeoffs between run-time efficiency and code legibility. I hadn't thought about implementing such a DLL for the particular optimized purpose of catching an arbitrary number of interfaces; I don't know whether there would be any advantage to including such functionality in the DLL, versus passing it a lambda expression or anonymous method to test whether an exception should be caught.

BTW, another feature a wrapper can provide which is otherwise missing in C# is the ability to report a double-fault condition (an exception occurs in the mainline, and another exception occurs during a subsequent "finally" block) without having to catch the initial exception. When exception occurs in a finally block, that's generally a bigger problem than an exception which occurs in the main-line, and should thus not be stifled, but allowing a "finally block" exception to percolate up the call stack would generally destroy any evidence of the original exception. While surrounding code will likely be more interested in the cleanup failure than the original exception, logging both exceptions is apt to be far more useful than stifling the original.

Share:
10,588
Daniel Bradley
Author by

Daniel Bradley

Software Engineer, working in London, specialising in C#, software architecture, MySQL and SOLR.

Updated on June 20, 2022

Comments

  • Daniel Bradley
    Daniel Bradley almost 2 years

    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?

  • xanatos
    xanatos over 12 years
    And how do you solve the difference between ArgumentNullException and ArgumentException if you put both catches? It's the same problem. (the second one inherits the first one). The ordering of the catches defines the relative priority of them.
  • Daniel Bradley
    Daniel Bradley over 12 years
    However, this can happen with parent and child exception types. In this case the framework uses the first catch statement, but then will execute the second catch statement if the first throws a type that also matches the second type.
  • Daniel Bradley
    Daniel Bradley over 12 years
    Yes, so having a default implementation of the interface is useful for reuse, but anyone implementing an interface has to implement all the relevant functions so could simply re-implement this themselves.
  • Lasse V. Karlsen
    Lasse V. Karlsen over 12 years
    You can't catch interfaces, and IExceptionA/B cannot be a class, in which case you don't have multiple inheritance. A new throw inside one catch won't be handled by the next on the same level, even if it matches the exception type. Both the answer and one of the comments here is wrong. Only one catch-block for each try/catch is used, and it's the first one that matches the exception type actually being thrown. So there is no ambiguity.
  • HIRSCHMANN Yann
    HIRSCHMANN Yann over 12 years
    It would choose the first block. Same logic as for normal exception types. If your first catch block catches Exception all others will be ignored. See my answer. I think your idea is right, but the main problem is performance.
  • codymanix
    codymanix over 12 years
    The first matching catch block would be used, as it is implemented with Exception type inheritance now..
  • Christian Palmstierna
    Christian Palmstierna over 12 years
    Having it in the base class ensures that said debug info will not be incorrect due to developer mistakes. Sure, you could just call the "default" implementation in your implementation but you could just as well not do it. If we take your argument one step further, why do we use class inheritance at all? We can just implement the interfaces and use the base implementation when it suits us.
  • xanatos
    xanatos over 12 years
    (+1 to Lasse) So knowledge of .NET isn't a lost science :-) But the first sentence is quite illogical. We are speaking of a "what if" world where you can catch interfaces.
  • Ankur
    Ankur over 12 years
    @Lasse V. Karlsen : The code in answer is for demo purpose showing what problem will happen in case of Interface based exceptions were allowed.
  • xanatos
    xanatos over 12 years
    +1 This is a quite good argument. I don't know if this would really be a problem. I would think the list of implemented classes/interfaces of an object is saved in its type as something similar to a Dictionary, so accessing it is only a little more expensive than checking the parent list but still...
  • Lasse V. Karlsen
    Lasse V. Karlsen over 12 years
    Ok, then I misunderstood that part. But then in a hypothetical world, anything could and would happen. I still think that the "first one wins" and "only one" would be present though.
  • Artur Mustafin
    Artur Mustafin over 12 years
    +1 Look into my post. Agreed, but in commin, I do not think it was to difficult to implement this in the compiler, i was difficult to introduce this to the standart of the language (see the "only interfaces" cause)
  • Daniel Bradley
    Daniel Bradley over 12 years
    Interesting idea, anyone got a reference to the performance differences between type checking of parent classes and interfaces?
  • Daniel Bradley
    Daniel Bradley over 12 years
    @lasse-v-karlsen yup, my bad.
  • xanatos
    xanatos over 12 years
    @Lasse Yup, true. The "first one wins" and "only one" are still good rules.
  • xanatos
    xanatos over 12 years
    @info_dev Done some times. 4 exception classes implementing an interface. pastebin.com/6RAE8dCW Same times (Release + Ctrl-F5 Run with no Debugger). The only thing to note is that the first run is a little slower (order isn't really important). Must be because if I remember correctly the type hierarchy is built only on request and then cached.
  • supercat
    supercat over 12 years
    @CPX: One could quite usefully define an ExceptionInfo object, and require that IException provide expose an Info property of type ExceptionInfo.
  • Christian Palmstierna
    Christian Palmstierna over 12 years
    @supercat: Yes, but that doesn't guarantee that the ExceptionInfo contains anything. I think it seems like a bad idea to leave the exception info to the implementer's discretion...
  • Daniel Bradley
    Daniel Bradley over 12 years
    +1 That's a very neat solution. It's a real shame this isn't supported in C#, hadn't seen that done before. Helpful reference for anyone else on this idea: blogs.msdn.com/b/jaredpar/archive/2008/10/09/…
  • supercat
    supercat over 12 years
    @CPX: Any Exception implementation which failed to supply a valid ExceptionInfo would be broken. It's perfectly possible today for an Exception class to override existing properties in totally bizarre and unreasonable ways that will break most "catch" code, but that doesn't seem to be a particular problem. Actually, as noted in my answer, I think catching interfaces would be less feeble than catching classes, but fundamentally I dislike the concept of using types as the primary determinant of what to catch.
  • supercat
    supercat over 12 years
    Catch-and-rethrow is different from not catching an exception. Rethrowing "ex" will lose all information in the stack trace; throwing without specifying "ex" is a little better; it will lose the line number where the current method called the routine that failed, but keep the rest of the stack trace. It still means that any nested cleanup code will be run before the exception is caught and rethrown, which can often be annoying from a debugging standpoint and can occasionally cause problems in production code.
  • supercat
    supercat over 12 years
    @info_dev: See the addendum above.
  • Christian Palmstierna
    Christian Palmstierna over 12 years
    @supercat: Would it? The purpose of an interface to allow differing implementations to a common contract. Conversely, the purpose of using a base class (or rather, one of the purposes) is to disallow differing implementations of particular features, which is exactly what is done in the Exception. The only thing a sub class can modify (without hacking) is the Message and InnerException properties.
  • Christian Palmstierna
    Christian Palmstierna over 12 years
    I think the issue of determining the severity of an exception is not something you could generalize in the way you describe (assuming I interpret you correctly). Whether or not you can recover from the error is very implementation dependent. If you fail to load a file because the user entered a path that does not exist, you can easily recover from it by just informing the user of his/her error. But if you attempt to load a configuration file which does not exist, the recovery is much more complex. Same error/exception, but completely different degrees of severity.
  • supercat
    supercat over 12 years
    @CPX: The code which tries to load a file would regard a failure to load which left the system in the same state as it was before the attempt as a "clean failure". Code which tries to load a configuration file and gets any failure--even a "clean one" while doing so--should escalate the "clean failure" into a more "severe" one. Ideally, the exception mechanism would allow the severity of the exception to be escalated without having to catch and rethrow, but nothing like that exists in .net for any of the standard exceptions, and implementing it with filter blocks would be clunky.
  • supercat
    supercat over 12 years
    @CPX: Subclasses can override ToString, StackTrace, Source, and Data, among other things.
  • Daniel Bradley
    Daniel Bradley over 12 years
    Very helpful, I was just looking at the data collection yesterday for doing this, and was drafting a new question to check the validity of the method. I do like the addition of the extension method for giving easy access. The only other addition I was thinking was to use a singleton object for the key as it's an object -> object collection rather than a string to avoid any possible conflicts with someone else using the same key.
  • Alex
    Alex over 12 years
    Refactored answer to extract string key - this value should be encapsulated with the extension method static class, since it should be accessed outside of this context, there fore private const
  • supercat
    supercat over 12 years
    How about writing something like the above in vb.net using "Catch...When", and coding it as a method-wrapper DLL, so as to get around C#'s lack of exception filters?
  • Prometheus
    Prometheus over 7 years
    this interface exists but if System.Exception does not inherit from it then there is no polymorphism to any exception class anywhere. Which is the case.
  • jwdonahue
    jwdonahue over 5 years
    It's only there for unmanaged code to get access to Exception data, not to be called from managed code.