Disposable Without Borders


    In my previous article, I talked about how an object can simply and reliably be responsible for its resources.

    But there are many ownership options that are not the personal responsibility of the property:
    • Resources owned by dependencies. When using Dependency Injection, a class object should not only not be responsible for the life cycle of its dependencies, it just cannot physically do this: the dependency can be shared between several clients, the dependency may implement IDisposable, but it may not, but it can have its own addictions 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 be responsible for yourself and for that guy (dependency)
    • Resources that do not need to be cleaned under certain conditions. This, for example, is a bad habit of StreamReader to close the underlying Stream when calling Dispose
    • Resources that are external to the dependency, but required by the client in the process of using it. The simplest example is subscribing to object events when assigning it to a property.


    Among the standard classes and .NET interfaces, there is no ready-made solution. But, fortunately, this bike is very easy to assemble yourself and it can give a convincing answer to all the requirements regarding the release of resources.

    New IDisposable: now with a generalization


        public interface IDisposable : IDisposable
        {
            T Value { get; }
        }
    

    The semantics of generalized IDisposable differs from the usual one in much the same way as “you can be free” from “immediately free the room”. Now the cleaning of resources is separated from the implementation of the basic functionality and can be determined both by the provider of the dependency and by its consumer.

    The implementation is simple as a moo:
        public class Disposable : IDisposable
        {
            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, with the help of a new bicycle and several single-line pieces of syntactic sugar, you can simply, cleanly and elegantly solve all the considered options for freeing resources.

    To get started, we’ll save ourselves from calling the constructor with an explicit type using the extension method:
            public static IDisposable ToDisposable(this T value, IDisposable lifetime)
            {
                return new Disposable(value, lifetime);
            }
    

    To use, just write:
            var disposableResource = resource.ToDisposable(disposable);
    

    The types of the 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 ToSelfDisposable(this T value) where T : IDisposable
            {
                return value.ToDisposable(value);
            }
    

    If you don’t need to delete anything, but they expect from us what we can do (remember the harmful StreamReader?):
            public static IDisposable ToEmptyDisposable(this T value) where T : IDisposable
            {
                return value.ToDisposable(Disposable.Empty);
            }
    

    If you want to automatically unsubscribe from object events when parting:
            public static IDisposable ToDisposable(this T value, Func 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 here one-liner will come to the rescue:
            public static IDisposable ToDisposable(this T value, Action dispose)
            {
                return value.ToDisposable(value, Disposable.Create(() => dispose(value)));
            }
    

    And even if special code is also needed for initialization:
            public static IDisposable ToDisposable(this T value, Func disposeFactory)
            {
                return new Disposable(value, Disposable.Create(disposeFactory(resource)));
            }
    

    Use 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 ready-made wrapper, but we need to add a little more responsibility for cleaning resources?
    No problems:
            public static IDisposable Add(this IDisposable disposable, IDisposable lifetime)
            {
                return disposable.Value.ToDisposable(Disposable.Create(disposable, lifetime));
            }
    


    Summary


    Having stumbled upon this idea right in the course of solving a business problem, I immediately wrote and with a feeling of deep satisfaction I applied all the considered single-line items.

    What is surprising, despite the presence of at least one complete analogue of IDisposablerepresented by Ownedfrom Autofac , cursory Google did not reveal similar extension methods.

    I hope the article and the application of its materials in practice will give readers no less pleasure than the author.
    Any additions or criticism are welcome.

    Also popular now: