
In my
previous article, I explained how an object can be simply and reliably responsible for its resources.
But there are many ownership options that are not the personal responsibility of the object:
- Resources owned by dependencies. When using Dependency Injection, a class object is not only not responsible for the life cycle of its dependencies, it simply cannot physically do this: a dependency can be shared between several clients, a dependency can realize IDisposable, and it can not realize it, but it can dependencies and so on. By the way, this argument immediately puts an end to any business interfaces that extend IDisposable: such an interface requires the impossible from its implementations - to answer for itself and for that guy (dependency)
- Resources that under certain conditions do not need to be cleared. This is, for example, the bad habit of StreamReader to close the underlying Stream when calling Dispose
- Resources that are external to the dependency, but are required by the client during its use. The simplest example is a subscription to an object event when assigning it to a property.
There are no ready solutions among standard classes and .NET interfaces. But, fortunately, this bike is very easy to assemble yourself and it will be able to give a convincing answer to all the requirements regarding the release of resources.
New IDisposable <T>: now with generalization
public interface IDisposable<out T> : IDisposable { T Value { get; } }
The semantics of generalized IDisposable differs from the usual in much the same way as “you can be free” from “immediately vacate the room”. Now, resource cleanup is separated from the implementation of the core functionality and can be determined by both the dependency provider and its consumer.
')
The implementation is as simple as mooing:
public class Disposable<T> : IDisposable<T> { public Disposable(T value, IDisposable lifetime) { _lifetime = lifetime; Value = value; } public void Dispose() { _lifetime.Dispose(); } public T Value { get; } private readonly IDisposable _lifetime; }
We use steroids
And now I will show how using a new bicycle and a few single-line pieces of syntactic sugar, you can simply, cleanly and elegantly solve all the options considered for freeing resources.
First, save yourself from calling the constructor with an explicit type indication using the extension method:
public static IDisposable<T> ToDisposable<T>(this T value, IDisposable lifetime) { return new Disposable<T>(value, lifetime); }
To use, simply write:
var disposableResource = resource.ToDisposable(disposable);
Types compiler in the lion’s share of cases will successfully display itself.
If the object already inherits IDisposable and this implementation suits us, then it is possible without arguments:
public static IDisposable<T> ToSelfDisposable<T>(this T value) where T : IDisposable { return value.ToDisposable(value); }
If you don’t need to delete anything, but they are expecting from us what we can do (remember about the harmful StreamReader?):
public static IDisposable<T> ToEmptyDisposable<T>(this T value) where T : IDisposable { return value.ToDisposable(Disposable.Empty); }
If you want to automatically unsubscribe from the object's events upon parting:
public static IDisposable<T> ToDisposable<T>(this T value, Func<T, IDisposable> lifetimeFactory) { return value.ToDisposable(lifetimeFactory(value)); }
... and apply like this:
var disposableResource = new Resource().ToDisposable(r => r.Changed.Subscribe(Handler));
If cleaning requires the execution of a special code, then one-liner will come to the rescue:
public static IDisposable<T> ToDisposable<T>(this T value, Action<T> dispose) { return value.ToDisposable(value, Disposable.Create(() => dispose(value))); }
And even if the special code is also needed for initialization:
public static IDisposable<T> ToDisposable<T>(this T value, Func<T, Action> disposeFactory) { return new Disposable<T>(value, Disposable.Create(disposeFactory(resource))); }
Using is even easier than telling:
var disposableViewModel = new ViewModel().ToDisposable(vm => { observableCollection.Add(vm); return () => observableCollection.Remove(vm); });
But what if we already have a finished wrapper, but we need to add a little more responsibility for cleaning up resources?
No problems:
public static IDisposable<T> Add<T>(this IDisposable<T> disposable, IDisposable lifetime) { return disposable.Value.ToDisposable(Disposable.Create(disposable, lifetime)); }
Results
Having stumbled upon this idea right in the course of solving a business problem, I immediately wrote and with a feeling of deep satisfaction applied all the considered one-liners.
What is surprising, despite the presence of at least one complete IDisposable <T> analogue in the person
Owned <T> from
Autofac , a cursory googling did not reveal similar extension methods.
I hope the article and the use of its materials in practice will give readers no less pleasure than the author.
Any additions and criticism are welcome.