roslyn: Proposal: Declarative approach to exception handling

disclaimer: I’m not a compiler guy so I have no clue what are the challenges (if any) to implement something like this as part of the language but I’m really curious and I would love to hear more about it from anyone in the community or the devs.

TL;DR Can we come up with ways to declarative approach to do exception handling? meaning that when we read a method, exception handling isn’t getting in our way to understand it.

Note! this proposal isn’t meant to be a replacement for the traditional try/catch/finally block but a syntactic sugar, so if you need more control, you can always fall back to the traditional way of doing it, choose the right tool for the job.

The technique I propose below was inspired by Job Kalb approach to refactor exception handling from methods in order to centralize it and make it reusable aka lippincott function, I just built on this idea with some more stuff.

I’ve written quite a bit of C# code over a decade and atm I’m writing more C++ code and in my research for exception handling in C++ for an application I’m writing I’ve discovered how to do exception handling in a more declarative way and because I really love C# when I do things in other languages or inspired by other languages I tend to think about how it would exist in C# just because…

I’ve watched these talks from Andrei Alexandrescu and Jon Kalb and some of the techniques they speak about are extremely powerful because they completely avoid the need for control flows especially when it comes to nested code and the nice thing about it is that it makes simple things simple to read and reason about.

They are speaking about exception handling in the context of C++ but I think that some of the concepts are equally viable in C# or any other language but I see no straightforward way to implement it in C# without winding the stack and keep the usage as simple as it is in C++ or D.

Currently to handle exceptions in C# we have the usual try/catch/finally control flow that adds quite a bit of noise to the function and hurts readability.

public T SomeIOoperation<T>() where T: class
{
    IO io = null;

    T result = default(T);

    try
    {
        io = new IO();

        result = io.DoSomething<T>();
    }
    catch(Exception ex) 
    {
        // Handle the exception
    }
    finally
    {
        io?.Dispose();
    }

    return result;
}

Now, what I’m suggesting is something like the following:

Updated the proposal based on the replies below and with some of my own thoughts.

private void ExceptionHandler(Exception ex) 
{
    // Handle any other exception
}

private void ExceptionHandler(IOException ex) 
{
    // Handle IO exception
}

public T SomeIOoperation<T>() where T: class
{
    IO io = null;

    try 
    {
        io = new IO();
        return io.DoSomething<T>();
    }
    catch ExceptionHandler // Points to a method group, single catch statement.
    finally io // Calls dispose implicitly. (Optional)

    return default(T);
}

How it works?

You define a single catch statement with the name of the exception handler, for each exception you want to handle you just overload the handler with the exception type as the first parameter.

It should be possible to encapsulate these handlers in a static class to create reusable handlers, like so:

static class IOExceptionHandlers
{
    public static void ExceptionHandler(IOException ex) { }
    public static void ExceptionHandler(Exception ex) { }
}

public T SomeIOoperation<T>() where T: class
{
    IO io = null;

    try 
    {
        io = new IO();
        return io.DoSomething<T>();
    }
    catch IOExceptionHandlers.ExceptionHandler
    finally io

    return default(T);
}

What would the compiler generate? The code that would be generated by the compiler is something like the following:

public T SomeIOoperation<T>() where T: class
{
    IO io = null;

    T result = default(T);

    try
    {
        io = new IO();

        result = io.DoSomething<T>();
    }
    catch(IOException ex)
    {
        ExceptionHandler(ex);
    }
    catch(Exception ex) 
    {
        ExceptionHandler(ex);
    }
    finally
    {
        io?.Dispose();
    }

    return result;
}

I’m not sure whether the call to ExceptionHandler is even necessary, maybe the compiler can inline this or something…

Advantages:

  1. It separates the task that the method needs to do from the responsibility of handling exceptions.
  2. It reduces the noise of exception handling when it’s needed and so it greatly helps in terms of readability and making sense of the code.
  3. It is an option for people who wants something in between the using statement and the traditional try/catch block.
  4. It helps making exception handling handlers more reusable so it also helps in terms of maintainability.

Disadvantages (Problems?):

Because the actual handling of the exception is done outside to the original function (caller) that raises the exception there are few downsides to this approach:

  1. The only information that the callee (the function that handles the exception) has is the exception, things like local variables won’t be available to provide more information which can be useful.
  2. It’s not possible to return out of the caller.
  3. It’s not possible to use break or continue in the caller.
  4. It’s not possible to rethrow meaning to use throw; however, throw ex is possible.

I don’t know how to solve all these problems and I don’t know whether they are actually problems but for this proposal to be useful the first and the last points are definitely important issues in my opinion.

More general resources about this topic:

  1. C++
  2. D

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 2
  • Comments: 19 (1 by maintainers)

Most upvoted comments

I don’t see why moving the exception handling outside of the method would make it easier to read. If anything it makes it more complicated since you then have to discern how the compiler might wire up the different handlers and then actually follow the handlers to where they perform their action. This type of syntax also completely precludes the ability to use exception filters (when) or patterns.

I don’t particularly care for the scope guard syntax of languages like D, for the same reasons that I don’t like defer (#8115). It becomes impossible to read what a method will do in the order in which it will be done since the deferred operations occur backwards. Scope guards up the ante in that you have to know which context under which the operation will occur. I think it’s pretty telling that both Swift’s and D’s example code are incredibly contrived and do not demonstrate why the feature has real-world value.

You also imply automatic disposal of IDisposable types, but this seems quite inappropriate because the compiler shouldn’t be making the determination as to when that type should be disposed, if ever. Why should the compiler assume that IO.DoSomething() isn’t then assigning IO to some instance elsewhere that would be used later or by another class? What of the types where disposing is intentionally optional may perform actions that aren’t always wanted? For example, StreamWriter is IDisposable and calling Dispose on it will close the underlying stream which is frequently enough not a desired behavior.