Disposable ref structs in C# 8.0

Among many things that are coming with the upcoming C# 8.0, one perfectly fits the topic of ref structs I’ve raised in my previous postdisposable ref structs.

As one of the blog posts announcing C# 8.0 changes (in Visual Studio 2019 Preview 2) mentions:

“Ref structs were introduced in C# 7.2, and this is not the place to reiterate their usefulness, but in return they come with some severe limitations, such as not being able to implement interfaces. Ref structs can now be disposable without implementing the IDisposable interface, simply by having a Dispose method in them.”

Indeed, as we should remember from my previous post, ref structs cannot implement interface because it would expose them to boxing possibility. But because of that we cannot make them implementing IDisposable, and thus we cannot use them in using statement:

The above code ends with compilation eerror

But from now on, if we add public Dispose method to our ref struct, it will be auto-magically consumed by the using statement and the whole thing compiles:

Even more, due to changes in the using statement itself (described in the mentioned blog post), we can now use more concise way of using… using (so-called using declarations):

But… why?

This is a long topic but in general explicit cleanup (deterministic finalization) is preferred over implicit one (not-deterministic finalization). This is somehow intuitive. It is better to explicitly make a cleanup as soon as it is possible (by calling Close, Dispose, or using statement), instead of waiting for non-explicit cleanup that will occur “at some time” (by the runtime executing finalizers).

Thus, when designing type owning some resource, we would like to have some explicit cleanup possibility. In C# the choice is obvious – we have a well-known contract in the form of IDisposable interface and its Dispose method.

Note. Please note that in case of ref structs the cleanup choice is also limited to explicit cleanup as ref structs cannot have finalizers defined.

Let’s take an example of a trivial “unmanaged memory pool wrapper” presented below, for illustrative purposes. Dedicated to performance-freaks, it is a ref struct to make it as lightweight as possible (no heap utilization ever):

As it owns unmanaged resource underneath, we introduced Dispose method to cleanup it at the end of usage. Thus, example usage could look like:

This is obviously cumbersome – we need to remember about calling Dispose. And obviously not-happy-path, like exception handling, is not properly handled here. This is why using statement was introduced, to make sure it will be called underneath. But as already said, till C# 8.0 it could not be used here.

But now in C# 8.0, without a problem, we can make use of all benefits from the using statement:

Even more, thanks to using declarations, our code becomes more concise:

Another two examples presented below (with a lot of code cut off for brevity) come from CoreFX repository.

The first example is ValueUtf8Converter ref struct that wraps around byte[] array from the array pool:

The second example is RegexWriter that wraps two ValueListBuilder ref structs that need to be explicitly cleaned (as they also manage arrays from the array pool):

In summary

We can treat disposable ref structs as lightweight types that have A REAL destructor, known from C++. It will be executed as soon as the corresponding instance goes out of the scope of the underlying using statement (or enclosing scope in case of using declaration).

For sure it is not a feature that will suddenly become extremely popular in writing regular, business-driven code. But few layers below, when writing high-performant low-level code, it is worth to know about such possibility!

4 comments

  1. Pingback: dotnetomaniak.pl
  2. Great summary. Especially the using declarations seem to be very useful. Ref structs look awesome for tracing method/enter leave scenarios. I have an existing library with a normal struct but it could cause issues if I make it ref struct because I am not so sure how this will look to the managed C++ compiler which was not updated for ages.

  3. But you can not prevent prevent s ValueUtf8Converter from being copied (for example by passing it to another function). The copy can then be Disposed()-d, and if the original then access its Span() you are in trouble…

Leave a Reply

Your email address will not be published. Required fields are marked *