17

Is there a way to extend the using block in C# in such a way that takes a delegate as a second parameter alongside an IDisposable object and executes every time when an exception is thrown inside that using block?

Imagine we have a delegate, something like this:

public delegate void ExceptionHandler(Exception ex);

And suppose I have a method that matches that delegate, something like this:

public void Log(Exception ex)
{
  // Some logging stuff goes here
}

And I want to accomplish something like this:

using(SqlConnection connection = new SqlConnection(""), Log)
{

}

Is there a way to extend C# in such a way?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • 2
    why arent you incapsulating the using block in a try block and call Log on a thrown exception? – downrep_nation Feb 28 '17 at 13:38
  • 2
    Is there a reason for you not to just use a try-catch block inside the using block? – Tamás Szabó Feb 28 '17 at 13:38
  • 3
    @TomaszJuszczak in the cases where `Dispose()` isn't called, exceptions can't be caught and logged anyway, so that's not really relevant. – Jon Hanna Feb 28 '17 at 13:40
  • 2
    A complete aside: Python's [context managers](http://book.pythontips.com/en/latest/context_managers.html) can do what you're looking for. The `__exit__` function is called whether there was an error or not, but in the event of an error, the error is passed as an argument to it. This means that context managers don't just act as `try`,`finally` blocks; they can also be used to encapsulate the functionality of an `except` block. – jpmc26 Feb 28 '17 at 19:31
  • Thank you for answer. I've just explored that in python.That's great. I hope this kind of functionality will be added to C# in the future versions :) –  Feb 28 '17 at 19:35
  • Well, the C# compiler is open-source, so... how much effort do you want to put in for a feature that can be easily gotten with other built-in features like `try`/`catch`? – Nic Feb 28 '17 at 21:16

3 Answers3

34

A using block is a shorthand for a try finally block with a call to Dispose in the finally. It can not be extended to be something more than that. What you want is the functionality of a try catch finally, so why not use exactly that:

SqlConnection connection = new SqlConnection("");
try {

}
catch (Exception exc) {
    Log(exc);
}
finally {
    connection.Dispose();
}

This comes with all the advantages of a try catch finally, for example catching multiple exception types and C# 6.0 exception filters. Consider this:

SqlConnection connection = new SqlConnection("");
try {

}
catch (SqlException exc) when (exc.Number > 0) {
    //Handle SQL error
}
catch (Exception exc) {
    Log(exc);
}
finally {
    connection.Dispose();
}

If you want to reuse standardized try catch finally blocks, you can use delegates:

static class ErrorHandler {
    public static ExecuteWithErrorHandling<T>(Func<T> createObject,
        Action<Exception> exceptionHandler, Action<T> operation) where T : IDisposable {

        T disposable = createObject();
        try {
            operation(disposable);
        }
        catch (Exception exc) {
            exceptionHandler(exc);
        }
        finally {
            disposable.Dispose();
        }
    }
}

Which you can call like:

ErrorHandler.ExecuteWithErrorHandling(() => new SqlConnection(""), Log, connection => {
    //Use connection here
});
Sefe
  • 13,731
  • 5
  • 42
  • 55
  • Thank you for answer. But i am looking for if there are any extension points in C#. `using` statement is converted into the code that you wrote in your answers. But i want to achieve something that my Log method is invoked inside catch block behind the scenes when i use using block. –  Feb 28 '17 at 13:49
  • There are no extension points of that type in C#. Your solution would also fall short of the functionalities of the existing `try catch finally` (check my latest edit). The only thing you can do is to wrap the whole `try catch finally` in a custom method. – Sefe Feb 28 '17 at 13:52
  • I agree with you but it would be a solution when we have only one catch block in our code. I wish this functionality will be added to C# in the future versions. –  Feb 28 '17 at 13:58
  • 11
    Since `try catch finally` does that already and even more than that, I don't think we'll see this coming ever. – Sefe Feb 28 '17 at 14:00
13

You can't extend the using statement but you can wrap it in a method:

void DoStuff(Action action, ExceptionHandler log)
{
    using(var connction = new SqlConnection(""))
    {
        try
        {
            action();
        }
        catch(Exception e)
        {
            log(e)
        }
    }
}
Zohar Peled
  • 79,642
  • 10
  • 69
  • 121
4

Just step back a bit with the syntactic sugar.

Since:

using(var obj = factory_or_constructor())
{
  // Do Stuff
}

is shorthand for the common pattern

obj = factory_or_constructor();
try
{
  // Do Stuff
}
finally
{
  ((IDisposable)obj)?.Dispose();
}

Then you could just change it to:

try
{
  // Do Stuff
}
catch(Exception ex)
{
  Log(ex);
  throw;
}
finally
{
  ((IDisposable)obj)?.Dispose();
}

But then it doesn't really offer much more over the simpler and clearer.

using(var obj = factory_or_constructor())
{
  try
  {
    // Do Stuff
  }
  catch(Exception ex)
  {
    Log(ex);
    throw;
  }
}

It's not really "extending using" but then if the point of using is to have a succinct syntax for a common pattern, it isn't as useful for it to have a succinct syntax for a rare pattern.

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
  • @xanatos true, though it's not clear to me whether the requirement is that it should or not. – Jon Hanna Feb 28 '17 at 13:51
  • nice explanation for newcomers, but there are 2 small issues: first, not `((IDisposable)obj)?.Dispose();` but `(obj as IDisposable)?.Dispose();` or you'll get castexceptions (yes, I know that `using` requires IDisposable, but the expanded form does not, so **at least** the **third** code sample should get this `as` form); second, in the last example, I'd rather write `try/using/catch` than `using/try/catch` like you have now, since `factory or constructor` **and also** `dispose` can throw and we probably want to have it logged as well. – quetzalcoatl Mar 01 '17 at 09:29
  • @quetzalcoatl I think I prefer the `(IDisposable)` cast here precisely because it would fail in such a case, though the `as` is in fact closer to the generated code (and often optimised away entirely). Whether failure in the dispose should be logged or not isn't clear from the question, so I don't know where it should be put to provide the requirement. – Jon Hanna Mar 02 '17 at 11:28