Go (https://golang.org) has a really nice little language feature called defer, which is a keyword that lets you defer a statement until the current function returns, and you can see an example here. Given all the new language features in C# 8.0, I wanted to see what it would look like to use this in C# today.

The Problem

So I have the following code:

static void
void
Specifies a return value type for a method that does not return a value.
MyMethod
void Example.MyMethod()
()
{
var
byte[]
buffer
byte[]? buffer
= ArrayPool
ArrayPool<byte>
Provides a resource pool that enables reusing instances of type T[].
<
ArrayPool<byte>
Provides a resource pool that enables reusing instances of type T[].
byte
byte
Represents an 8-bit unsigned integer.
>
ArrayPool<byte>
Provides a resource pool that enables reusing instances of type T[].
.Shared
ArrayPool<byte> ArrayPool<byte>.Shared
Gets a shared ArrayPool`1 instance.
A shared ArrayPool`1 instance.
.Rent
byte[] ArrayPool<byte>.Rent(int minimumLength)
Retrieves a buffer that is at least the requested length.
minimumLength — The minimum length of the array.
An array of type T that is at least minimumLength in length.
(81920);
try
{
// do some stuff with buffer
}
finally
{
ArrayPool
ArrayPool<byte>
Provides a resource pool that enables reusing instances of type T[].
<
ArrayPool<byte>
Provides a resource pool that enables reusing instances of type T[].
byte
byte
Represents an 8-bit unsigned integer.
>
ArrayPool<byte>
Provides a resource pool that enables reusing instances of type T[].
.Shared
ArrayPool<byte> ArrayPool<byte>.Shared
Gets a shared ArrayPool`1 instance.
A shared ArrayPool`1 instance.
.Return
void ArrayPool<byte>.Return(byte[] array, bool clearArray = false)
Returns an array to the pool that was previously obtained using the Int32) method on the same ArrayPool`1 instance.
array — A buffer to return to the pool that was previously obtained using the Int32) method.
clearArray — Indicates whether the contents of the buffer should be cleared before reuse. If clearArray is set to , and if the pool will store the buffer to enable subsequent reuse, the Boolean) method will clear the array of its contents so that a subsequent caller using the Int32) method will not see the content of the previous caller. If clearArray is set to or if the pool will release the buffer, the array's contents are left unchanged.
(buffer
byte[]? buffer
);
}
}

In order to avoid a memory leak, we must return the buffer to the pool, but to ensure this happens under all possible circumstances we have to use a try and finally block, and this adds some indentation, and noise that we don’t really want.

An appropriate solution to this particular problem today would be to wrap the rented buffer in an object that represents the resource and have it implement IDisposable. Using C# 8.0 new using syntax we can do:

static void
void
Specifies a return value type for a method that does not return a value.
MyMethod
void Example.MyMethod()
()
{
using var
PooledMemory
buffer
PooledMemory? buffer
= new PooledMemory
PooledMemory
(81920);
// do some stuff with buffer
}

Now the Dispose method on PooledMemory will be called implicitly at the end of the method block just as if we used a try/finally block, and I have less indentation and fewer curly braces.

However imagine if we had some cleanup task, that didn’t really warrant encapsulating in a type, so random cleanup, diagnostic logging, whatever. We don’t have a simple way to do this in the language.

Building a Defer Method

Let’s start by having an IDisposable struct that takes an Action:

static DeferDisposable
Example.DeferDisposable
Defer
Example.DeferDisposable Example.Defer(Action action)
(Action
Action
Encapsulates a method that has no parameters and does not return a value.
action
Action action
) => new DeferDisposable
Example.DeferDisposable
(action
Action action
);
internal readonly struct DeferDisposable
Example.DeferDisposable
:
Example.DeferDisposable
IDisposable
IDisposable
Provides a mechanism for releasing unmanaged resources.
{
readonly Action
Action
Encapsulates a method that has no parameters and does not return a value.
_action
Action Example.DeferDisposable._action
;
public DeferDisposable
Example.DeferDisposable.DeferDisposable(Action action)
(Action
Action
Encapsulates a method that has no parameters and does not return a value.
action
Action action
) => _action
Action Example.DeferDisposable._action
= action
Action action
;
public void
void
Specifies a return value type for a method that does not return a value.
Dispose
void Example.DeferDisposable.Dispose()
() => _action
Action Example.DeferDisposable._action
();
}

I have chosen to use a struct here so that there’s just a little less heap allocation, as it turns out that using will not box a struct when calling Dispose, see here.

Now let’s go back to the calling code:

static void
void
Specifies a return value type for a method that does not return a value.
MyMethod
void Example.MyMethod()
()
{
var
byte[]
buffer
byte[]? buffer
= ArrayPool
ArrayPool<byte>
Provides a resource pool that enables reusing instances of type T[].
<
ArrayPool<byte>
Provides a resource pool that enables reusing instances of type T[].
byte
byte
Represents an 8-bit unsigned integer.
>
ArrayPool<byte>
Provides a resource pool that enables reusing instances of type T[].
.Shared
ArrayPool<byte> ArrayPool<byte>.Shared
Gets a shared ArrayPool`1 instance.
A shared ArrayPool`1 instance.
.Rent
byte[] ArrayPool<byte>.Rent(int minimumLength)
Retrieves a buffer that is at least the requested length.
minimumLength — The minimum length of the array.
An array of type T that is at least minimumLength in length.
(81920);
using var
DeferDisposable
_
DeferDisposable _
= Defer
DeferDisposable Example.Defer(Action action)
(() => ArrayPool
ArrayPool<byte>
Provides a resource pool that enables reusing instances of type T[].
<
ArrayPool<byte>
Provides a resource pool that enables reusing instances of type T[].
byte
byte
Represents an 8-bit unsigned integer.
>
ArrayPool<byte>
Provides a resource pool that enables reusing instances of type T[].
.Shared
ArrayPool<byte> ArrayPool<byte>.Shared
Gets a shared ArrayPool`1 instance.
A shared ArrayPool`1 instance.
.Return
void ArrayPool<byte>.Return(byte[] array, bool clearArray = false)
Returns an array to the pool that was previously obtained using the Int32) method on the same ArrayPool`1 instance.
array — A buffer to return to the pool that was previously obtained using the Int32) method.
clearArray — Indicates whether the contents of the buffer should be cleared before reuse. If clearArray is set to , and if the pool will store the buffer to enable subsequent reuse, the Boolean) method will clear the array of its contents so that a subsequent caller using the Int32) method will not see the content of the previous caller. If clearArray is set to or if the pool will release the buffer, the array's contents are left unchanged.
(buffer
byte[]? buffer
));
// do some stuff with buffer
}

We are using the discard feature here (var _) too as we don’t care about the value, we just want it for the purpose of applying using. Edit: As pointed out in the comments, this isn’t a discard, but rather a variable declaration of the variable _.

Reducing Allocations

The above solution works and is fine, however we are capturing the buffer variable here within the lambda, this results in the compiler allocating a Action instance for every call, if we can avoid any references outside of the lambda then the compiler can avoid this and only create the Action once for the entire lifetime of application.

We can avoid capturing variables by creating overloads for the N parameters we need to reference within the lambda.

static DeferDisposable
Example.DeferDisposable<T>
<
Example.DeferDisposable<T>
T
T
>
Example.DeferDisposable<T>
Defer
Example.DeferDisposable<T> Example.Defer<T>(Action<T> action, T param1)
<
Example.DeferDisposable<T> Example.Defer<T>(Action<T> action, T param1)
T
Example.DeferDisposable<T> Example.Defer<T>(Action<T> action, T param1)
>
Example.DeferDisposable<T> Example.Defer<T>(Action<T> action, T param1)
(Action
Action<T>
Encapsulates a method that has a single parameter and does not return a value.
obj — The parameter of the method that this delegate encapsulates.
<
Action<T>
Encapsulates a method that has a single parameter and does not return a value.
obj — The parameter of the method that this delegate encapsulates.
T
T
>
Action<T>
Encapsulates a method that has a single parameter and does not return a value.
obj — The parameter of the method that this delegate encapsulates.
action
Action<T> action
, T
T
param1
T param1
) =>
new DeferDisposable
Example.DeferDisposable<T>
<
Example.DeferDisposable<T>
T
T
>
Example.DeferDisposable<T>
(action
Action<T> action
, param1
T param1
);
internal readonly struct DeferDisposable
Example.DeferDisposable<T1>
<
Example.DeferDisposable<T1>
T1
T1
>
Example.DeferDisposable<T1>
:
Example.DeferDisposable<T1>
IDisposable
IDisposable
Provides a mechanism for releasing unmanaged resources.
{
readonly Action
Action<T1>
Encapsulates a method that has a single parameter and does not return a value.
obj — The parameter of the method that this delegate encapsulates.
<
Action<T1>
Encapsulates a method that has a single parameter and does not return a value.
obj — The parameter of the method that this delegate encapsulates.
T1
T1
>
Action<T1>
Encapsulates a method that has a single parameter and does not return a value.
obj — The parameter of the method that this delegate encapsulates.
_action
Action<T1> Example.DeferDisposable<T1>._action
;
readonly T1
T1
_param1
T1 Example.DeferDisposable<T1>._param1
;
public DeferDisposable
Example.DeferDisposable<T1>.DeferDisposable(Action<T1> action, T1 param1)
(Action
Action<T1>
Encapsulates a method that has a single parameter and does not return a value.
obj — The parameter of the method that this delegate encapsulates.
<
Action<T1>
Encapsulates a method that has a single parameter and does not return a value.
obj — The parameter of the method that this delegate encapsulates.
T1
T1
>
Action<T1>
Encapsulates a method that has a single parameter and does not return a value.
obj — The parameter of the method that this delegate encapsulates.
action
Action<T1> action
, T1
T1
param1
T1 param1
) => (_action
Action<T1> Example.DeferDisposable<T1>._action
, _param1
T1 Example.DeferDisposable<T1>._param1
) = (action
Action<T1> action
, param1
T1 param1
);
public void
void
Specifies a return value type for a method that does not return a value.
Dispose
void Example.DeferDisposable<T1>.Dispose()
() => _action
Action<T1> Example.DeferDisposable<T1>._action
.Invoke
void Action<T1>.Invoke(T1 obj)
(_param1
T1 Example.DeferDisposable<T1>._param1
);
}

Unfortunately there is no way to explicitly ensure no variable capture happens, there is no static keyword for lambdas yet. In the calling code, you’ll have to be careful not to accidentally capture variables from the outer scope.

Here is the usage:

static void
void
Specifies a return value type for a method that does not return a value.
MyMethod
void Example.MyMethod()
()
{
var
byte[]
buffer
byte[]? buffer
= ArrayPool
ArrayPool<byte>
Provides a resource pool that enables reusing instances of type T[].
<
ArrayPool<byte>
Provides a resource pool that enables reusing instances of type T[].
byte
byte
Represents an 8-bit unsigned integer.
>
ArrayPool<byte>
Provides a resource pool that enables reusing instances of type T[].
.Shared
ArrayPool<byte> ArrayPool<byte>.Shared
Gets a shared ArrayPool`1 instance.
A shared ArrayPool`1 instance.
.Rent
byte[] ArrayPool<byte>.Rent(int minimumLength)
Retrieves a buffer that is at least the requested length.
minimumLength — The minimum length of the array.
An array of type T that is at least minimumLength in length.
(81920);
using var
DeferDisposable<byte[]>
_
DeferDisposable<byte[]> _
= Defer
DeferDisposable<byte[]> Example.Defer<byte[]>(Action<byte[]> action, byte[] param1)
(b
byte[] b
=> ArrayPool
ArrayPool<byte>
Provides a resource pool that enables reusing instances of type T[].
<
ArrayPool<byte>
Provides a resource pool that enables reusing instances of type T[].
byte
byte
Represents an 8-bit unsigned integer.
>
ArrayPool<byte>
Provides a resource pool that enables reusing instances of type T[].
.Shared
ArrayPool<byte> ArrayPool<byte>.Shared
Gets a shared ArrayPool`1 instance.
A shared ArrayPool`1 instance.
.Return
void ArrayPool<byte>.Return(byte[] array, bool clearArray = false)
Returns an array to the pool that was previously obtained using the Int32) method on the same ArrayPool`1 instance.
array — A buffer to return to the pool that was previously obtained using the Int32) method.
clearArray — Indicates whether the contents of the buffer should be cleared before reuse. If clearArray is set to , and if the pool will store the buffer to enable subsequent reuse, the Boolean) method will clear the array of its contents so that a subsequent caller using the Int32) method will not see the content of the previous caller. If clearArray is set to or if the pool will release the buffer, the array's contents are left unchanged.
(b
byte[] b
), buffer
byte[]? buffer
);
// do some stuff with buffer
}

Wrapping up

So it turns out we can do this with C# 8.0, however it’s not particularly concise or idiomatic. If we want it to have practically no performance cost as well, it’s even less concise. So I don’t recommend trying this out in your code, this was just a thought experiment.

There is a language proposal for proper support of this in C# here, and it could make the C# 9 timeframe. So if this interests you, be sure to subscribe to that issue 👀.