In the last several posts, I've introduced a resource management pattern I call RIFL, or Resource Is Function-Local. I've given examples in Go, Perl, and C++, and compared to RAII. Here, I'll give examples in C#, and discuss how it relates to the IDisposable pattern in C#.
In C#, a resource that needs management signals that to the coder by implementing the interface IDisposable. That contains a single function, void Dispose(), which is about as simple as interfaces get. Implementing IDisposable means that you should ensure that Dispose() gets called when you're done with the resource. And the language provides the "using" statement, which is some lovely syntax making calling Dispose() easy. A typical usage looks like this:
using (var resource = new Resource()) {
resource.Use();
} // implicitly calls resource.Dispose()
This is roughly equivalent to
var resource = new Reource();
try {
resource.Use();
} finally {
resource.Dispose();
}
The differences are mostly too subtle to affect our discussion here.
Often, as with RAII, you want some resource to have the same lifetime as an instance of a class. In that case, you construct the IDisposable resource in the constructor for the new class, and you have that class also implement IDisposable and call Dispose() on the resource in its own Dispose() method. Actually, there are subtle, confusing and dangerous interactions between Dispose(), the actual destructor (well, finalizer), the garbage collector and OS resources, which make correctly implementing Dispose() rather tricky. Fortunately, for our purposes, all of these issues are too subtle to affect our discussion much.
I'm not comfortable really talking much about Java, as I'm much more familiar with C#, but recent versions of Java have an AutoCloseable interface and an extension to the "try" syntax which is similar to Disposable and "using".
C# has similar flexibility to C++ because Disposable is so similar to RAII. Here's a few examples of using resources and non-resources in C#:
public void ExampleUsingResource() {
using (var resource = new Resource() {
resource.Use();
}
}
public Resource ExampleUseOfEscapingResource() {
var resource = new Resource();
resource.Use();
return resource;
}
public void ExampleUseOfLeakingResource() {
var resource = new Resource();
resource.Use();
} // failed to release resource properly by calling Dispose()
public void ExampleUseOfNonResource() {
var nonResource = new NonResource();
nonResource.Use();
} // no need to release nonResource, since it does not implement IDisposable
So this is even trickier than in C++, since leaking a resource not only looks like a cross between "using" a resource and deliberately allowing a resource to escape the current scope; but it looks identical to using a non-resource. And here's where it gets really tricky: the difference between a non-resource and a resource is that a resource implements IDisposable. And how do you know that when you're coding? Well, if you're using Visual Studio with IntelliSense (or something roughly equivalent), usually you would try typing "resource.Disp" and seeing if the system wants to autocomplete it to Dispose. This almost works, except for three issues.
First, and pretty minor, is that it's possible to implement Dispose without implementing IDisposable, and then you can't use "using". But you find that out as soon as you try to use a "using" statement, so that's mostly ok.
Second, and pretty tricky, is that some classes explicitly implement IDisposable.Dispose, so that the Dispose method isn't available unless you cast to (IDisposable). That means it won't autocomplete so you can be fooled into not using "using".
Third, sometimes it is apparently safe not to call Dispose on certain IDisposable objects, such as Task. This turns out to be pretty convenient, since in many of the ways you are encouraged to use Tasks in recent C# versions, it's very hard to find the right way to Dispose them. But this means that sometimes the Leaking code doesn't really leak, which is pretty confusing.
Oh, and did I mention that sometimes Dispose throws?
What it adds up to is that the IDisposable pattern seems fine, but ends up being really painful to use safely. Fortunately, C# has pretty good lambda support, so you can easily implement and use RIFL.
public class RiflResource {
public static void WithResource(Action<RiflResource> useFunc) {
using (var raw = RawResource.Obtain()) { // often: new RawResource()
var rifl = new RiflResource(raw);
useFunc(rifl);
} // implicitly Dispose (Release)
}
public void Use() { raw_.Use(); }
private readonly RawResource raw_;
private RiflResource(RawResource raw) { raw_ = raw; }
}
And it's easy to use
public void ExampleRiflUsage() {
RiflResource.WithResource(resource => {
resource.Use();
});
}
In fact, of the languages I've used in examples, C# has the best syntax for anonymous functions, which makes this the cleanest usage. And I intentionally wrote my example so that it's obvious how to add multiple calls to Use; this simple case has an even briefer alternate syntax:
public void ExampleRiflUsage() {
RiflResource.WithResource(resource => resource.Use());
}
or even
public void ExampleRiflUsage() {
RiflResource.WithResource(Use);
}
but that only works if you want to call Use() alone once with no arguments.
I am really happy to wrap up my posts on RIFL. So far, I've only blogged on 2 topics, and each of those turned into multi-post discussions, where each post was too long. I hope to find a reasonable-length topic for the next post.
No comments:
Post a Comment