renatoheeb.com

A functional approach to the using statement in C#

Intro

Lately I participated in a job interview and was questioned about the C# 8 "using declaration":

string lines=@"This is line one
This is line two";

using var reader = new StringReader(lines);
string? item;
do {
    item = reader.ReadLine();
    Console.WriteLine(item);
} while(item != null);

I'm pretty familiar with the "using statement" and the underlying concept, but didn't know about the new "using declaration" which was introduced in C# 8 and failed to answer the question, whether a given code snippet will compile or not. When you're not familiar with the new approach, you'll miss the braces and you'll think the Roslyn compiler will fail.

While I was reading a few articles about the motivation, the scope and the pro and cons of the using declaration I realized that nobody mentioned alternative approaches to the underlying challenge - to encapsulate setup and teardown operations.

Interestingly enough I'm currently reading "Functional Programming in C#" and the author Enrico Buonanno shows in an inspiring way, how to encapsulate setup and teardown operations into a HOF (Higher Order Function) to avoid code duplication.

In the following blog post I'll discuss some aspects of the using keyword and introduce "A functional approach to the using statement in C#".

If you're up to more than an introduction, I totally recommend the aforementioned book Functional Programming in C#.

You can find the repository with the following illustrations on GitHub: https://github.com/heebinho/fp-using

First off, using vs using

C# has different meanings for the using keyword, which in my opinion isn't ideal but not to bad either, because the meaning is pretty obvious in relation to it's context.

using directive:

using System;

using alias directive:

using Excel = Microsoft.Office.Interop.Excel;

using static directive:

using static System.Math;

public double Circumference
    => PI * 2 * Radius

using static enables unqualified access to the static members and was introduced in C# 6 to improve readability.
(PI in the above example)

As mentioned in the introduction C# 8 introduced the using declaration which is related to the following using statement:

string manyLines=@"This is line one
This is line two";

using (var reader = new StringReader(manyLines))
{
    string? item;
    do {
        item = reader.ReadLine();
        Console.WriteLine(item);
    } while(item != null);
}

Pretty similar to the using declaration showed in the beginning. The using declaration just doesn't require the braces. So I guess many developers will prefer the new using declaration to improve readability. But what happens under the hood ...

Disposing Resources with the traditional using statement

The primary use of the IDisposable interface is to release unmanaged resources. You can use the using statement instead of explicitly calling Dispose() yourself as illustrated in the following example:

using System;
using Xunit;

public class DisposableResource : IDisposable
{
    private bool disposed = false;

    public void Dispose()
    {
        Console.WriteLine($"Dispose {T}");
        disposed = true;
    }
    public void Do() => TryWrite();

    void TryWrite()
    {
        if (disposed) throw new ObjectDisposedException("object disposed.");
        Console.WriteLine($"Write: {T}");
    }

    public int T => DateTime.Now.Millisecond;
}

public class DisposableResourceTest
{
    [Fact]
    public void ShouldDisposeRessource()
    {
        var resource = new DisposableResource();
        Action a = () => resource.Do();
        using (resource){ a(); }
        Assert.Throws<ObjectDisposedException>(a);
    }
}
$ dotnet test --filter "DisposableResourceTest"
-->
A total of 1 test files matched the specified pattern.
Write: 637291702458775586
Dispose: 637291702458872992

Test Run Successful.
Total tests: 1
     Passed: 1

If you look at the output, you can see that the Dispose() method was called. The compiler wraps the operations of the using block in a try/finally block. You can verify it, if you look at the generated IL code:

.try
  {
    //e.g
    IL_0019:  leave.s    IL_0026
  }  // end .try
  finally
  {
    IL_001b:  ldloc.0
    IL_001c:  brfalse.s  IL_0025
    IL_001e:  ldloc.0
    IL_001f:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
    IL_0024:  nop
    IL_0025:  endfinally
  }  // end handler

You can see the try/finally block and the call of the Dispose() method.

Functional approach

When we use the using statement or the using declaration we don't need to wrap our code into a try/finally block and we don't need to call the Dispose() method ourselves. In essence there are three steps which are executed in the following order:

  1. Acquiring a resource implementing IDisposable
  2. Execute operations in scope {}
  3. Dispose resource

Whereas the first and the last step are always identical i.e. boilerplate. We can raise the abstraction level and use a function f() to represent the required operations and the terms setup/teardown to contrive a common use case for a HOF:

  1. Setup
  2. f()
  3. Teardown

C# supports functions as first-class values, which means that we can write a function f() that takes an IDisposable and a function g() as arguments:

g: IDisposable -> R
f: (IDisposable, g()) -> R
f: (IDisposable, (IDisposable -> R)) -> R

A possible implementation could look like this:

public static R Using<TDisp, R>(TDisp disposable
    , Func<TDisp, R> func) where TDisp : IDisposable
{
    using (var disp = disposable) return func(disp);
}

Or now, that we know that the compiler will call the Dispose() method in the finally block, we could remove the using keyword entirely:

public static R Using<TDisp, R>(TDisp disposable
    , Func<TDisp, R> func) where TDisp : IDisposable
{
        try
        {
            return func(disposable);
        }
        finally
        {
            disposable?.Dispose();
        }
}

This generic Using method takes two arguments - a disposable resource, and a function to be executed before the resource is disposed. Now we have everything in place to take advantage of the Using HOF:

public class DisposableResourceTest
{
    [Fact]
    public void ShouldDisposeRessourceFP()
    {
        var resource = new DisposableResource();
        Action<DisposableResource> a = (r) => resource.Do();

        //An extension method which turns an Action<T> into a Func<T, R>
        Func<DisposableResource, Unit> f = a.ToFunc(); 

        //Now we can use our Using method as intended
        Using(resource, f); 

        //The resource is disposed and should throw an ObjectDisposedException
        Assert.Throws<ObjectDisposedException>(()=>resource.Do());
    }
}

In the above example you can see, that it is pretty straightforward to leverage the custom Using function. One side note: The Using function expects a function as the second argument and not an action, this is why we have to transform the Action<T> into a Function<T, Unit> with the help of the ToFunc() extension method:

using System;
using Unit = System.ValueTuple;
using static F;

public partial static class F{
    public static Unit Unit() => default(Unit);
}

public static class ActionExtensions{
    public static Func<Unit> ToFunc(this Action action)
        => () => { action(); return Unit(); };
    
    public static Func<T, Unit> ToFunc<T>(this Action<T> action)
        => (t) => { action(t); return Unit(); };
}

The Unit() method returns a System.ValueTuple and means "no return value".

Closing thoughts

Although it feels a bit radical it's definitely an elegant solution to a recurring problem and has some advantages:

There are also some downsides:

For me there's conceptually much more to learn in the fp world and presumably I'll write about some topics in the future.