Disposables

Tomáš Holan       27.01.2011       Architektura       10948 zobrazení

Minule jsme si ukázali postup jak obejít nutnost implementace konkrétní třídy pro implementaci jednoduchého interface, což může být v některých případech docela užitečné.

Dnes si ukážeme ještě jeden příklad využití stejného principu. Jedná se o anonymní implementaci IDisposable. Přestože je typicky tento interface implementován nějakým konkrétním objektem, který vyžaduje deterministický úklid (uvolnění používaných zdrojů, odregistrace událostí apod.), je možné využití tohoto interface zobecnit. Tím mám namysli to, že jakákoliv svým charakterem “úklidová” akce může být např. u nějakého API předávána “zapouzdřena” právě do podoby typu IDisposable místo např. jen obyčejné Action. Výhoda je v tom, že je pak z kódu na první pohled patrný účel takové akce, klient může použít klíčové slovo using apod.

Tohoto principu právě poměrně masivně využívá knihovna Reactive Extensions for .NET (Rx) (kde se interface IDisposable konkrétně používá pro rušení registrací IObservable zdrojů). Součástí této knihovny jsou k tomu implementované pomocné třídy v namespace System.Disposables. Disposables konkrétně u verze Rx pro .NET Framework 4.0 nebo Silverlight sedí v assembly System.CoreEx.dll. A protože jsou tyto třídy public, dají se, kromě toho že jsou využívány interně v knihovně samotné, použít i zvenku.

Základem je třída Disposable, jejíž implementace je právě slíbený nám již známý pattern:

namespace System.Disposables
{
    internal sealed class AnonymousDisposable : IDisposable
    {
        #region member varible and default property initialization
        private readonly Action dispose;
        private bool isDisposed;
        #endregion

        #region constructors and destructors
        public AnonymousDisposable(Action dispose)
        {
            this.dispose = dispose;
        }
        #endregion

        #region action methods
        public void Dispose()
        {
            if (!this.isDisposed)
            {
                this.isDisposed = true;
                this.dispose();
            }
        }
        #endregion
    }

    internal sealed class DefaultDisposable : IDisposable
    {
        #region member varible and default property initialization
        public static readonly DefaultDisposable Instance = new DefaultDisposable();
        #endregion

        #region constructors and destructors
        private DefaultDisposable() { }
        #endregion

        #region action methods
        public void Dispose() { }
        #endregion
    }

    public static class Disposable
    {
        #region action methods
        public static IDisposable Create(Action dispose)
        {
            if (dispose == null)
            {
                throw new ArgumentNullException("dispose");
            }
            return new AnonymousDisposable(dispose);
        }
        #endregion

        #region property getters/setters
        public static IDisposable Empty
        {
            get { return DefaultDisposable.Instance; }
        }
        #endregion
    }
}

(Pozn.: Implementace je uvedená přesně tak jak je obsažena v knihovně Rx. Pokud by jsme jí chtěli dát do vlastní assembly pouze pro interní použití uvnitř dané assembly (tj. nereferencovali System.CoreEx.dll), vložíme obě třídy AnonymousDisposable a DefaultDisposable jako private do třídy Disposable a tu uděláme pouze internal.)

Kromě metody Disposable.Create(), která umožňuje převést libovolnou akci do metody Dispose() vraceného anonymního IDisposable, tu máme rovnou navíc zabudovanou “logiku” pro to, aby se dispose akce volala maximálně 1x (proměnná isDisposed ve třídě AnonymousDisposable).

Dále je tu vlastnost Disposable.Empty, která vrací sigleton instanci IDisposable s prázdnou implementací metody Dispose(). To se může hodit v případě, že API obecně vyžaduje vrátit nějaké IDisposable (vracení hodnoty null se od API neočekává) a žádný úklid v dané situaci nutný není.

Kromě třídy Disposable obsahuje namespace System.Disposables další třídy implementující IDisposable, nejdůležitější jsou tyto:

  • BooleanDisposable nedělá při vlastním Dispose() nic, ale umožnuje později přes vlastnost IsDisposed zjistit, že k volání Dispose() už došlo.
public sealed class BooleanDisposable : IDisposable
{
    #region member varible and default property initialization
    public bool IsDisposed { get; private set; }
    #endregion		

    #region action methods
    public void Dispose()
    {
        this.IsDisposed = true;
    }
    #endregion
}
  • CancellationDisposable volá při svém Dispose() metodu Cancel() na CancellationTokenSource předaný do konstruktoru.
public sealed class CancellationDisposable : IDisposable
{
    #region member varible and default property initialization
    private CancellationTokenSource cts;
    #endregion

    #region constructors and destructors
    public CancellationDisposable() : this(new CancellationTokenSource()) { }

    public CancellationDisposable(CancellationTokenSource cts)
    {
        if (cts == null)
        {
            throw new ArgumentNullException("cts");
        }
        this.cts = cts;
    }
    #endregion

    #region action methods
    public void Dispose()
    {
        this.cts.Cancel();
    }
    #endregion

    #region property getters/setters
    public CancellationToken Token
    {
        get { return this.cts.Token; }
    }
    #endregion
}

(Pozn.: Dvojice typů CancellationTokenSource a CancellationToken tvoří implementaci obecného cooperative cancellation patternu, který je používán od .NET Frameworku 4.0)

  • CompositeDisposable je vlastně kolekce jiných IDisposable, na které se pak zavolá Dispose() při Dispose() na celé kolekci. Prvky kolekce lze předat do konstruktoru nebo přidávat i kdykoliv později. Pokud je nějaký prvek přidáván až po volaní Dispose() na kolekci, je místo přidání na něj rovnou také jen zavoláno Dispose().
public sealed class CompositeDisposable : ICollection<IDisposable>,
    IEnumerable<IDisposable>, System.Collections.IEnumerable, IDisposable
{
    #region member varible and default property initialization
    private List<IDisposable> disposables;
    private bool disposed;
    #endregion

    #region constructors and destructors
    public CompositeDisposable(params IDisposable[] disposables)
    {
        if (disposables == null)
        {
            throw new ArgumentNullException("disposables");
        }
        IDisposable[] disposableArray = disposables;
        for (int i = 0; i < disposableArray.Length; i++)
        {
            if (disposableArray[i] == null)
            {
                throw new ArgumentOutOfRangeException("disposables");
            }
        }
        this.disposables = new List<IDisposable>(disposables);
    }
    #endregion

    #region action methods
    public void Clear()
    {
        lock (this.disposables)
        {
            foreach (IDisposable disposable in this.disposables)
            {
                disposable.Dispose();
            }
            this.disposables.Clear();
        }
    }

    public bool Contains(IDisposable disposable)
    {
        lock (this.disposables)
        {
            return this.disposables.Contains(disposable);
        }
    }

    public void Add(IDisposable disposable)
    {
        if (disposable == null)
        {
            throw new ArgumentNullException("disposable");
        }
        bool disposed = false;
        lock (this.disposables)
        {
            disposed = this.disposed;
            if (!this.disposed)
            {
                this.disposables.Add(disposable);
            }
        }
        if (disposed)
        {
            disposable.Dispose();
        }
    }

    public bool Remove(IDisposable disposable)
    {
        if (disposable == null)
        {
            throw new ArgumentNullException("disposable");
        }
        bool removed = false;
        lock (this.disposables)
        {
            if (!this.disposed)
            {
                removed = this.disposables.Remove(disposable);
            }
        }
        if (removed)
        {
            disposable.Dispose();
        }
        return removed;
    }

    public void CopyTo(IDisposable[] array, int arrayIndex)
    {
        if (array == null)
        {
            throw new ArgumentNullException("array");
        }
        if (arrayIndex < 0 || arrayIndex >= array.Length)
        {
            throw new IndexOutOfRangeException();
        }
        lock (this.disposables)
        {
            Array.Copy(this.disposables.ToArray(), 0, array, arrayIndex,
                       array.Length - arrayIndex);
        }
    }

    public void Dispose()
    {
        lock (this.disposables)
        {
            if (!this.disposed)
            {
                this.disposed = true;
                this.Clear();
            }
        }
    }

    public IEnumerator<IDisposable> GetEnumerator()
    {
        lock (this.disposables)
        {
            return ((IEnumerable<IDisposable>)this.disposables.ToArray()).GetEnumerator();
        }
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
    #endregion

    #region private member functions
    public int Count
    {
        get { return this.disposables.Count; }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }
    #endregion
}
  • MutableDisposable obdoba CompositeDisposable, ale umožňuje opakovaně nastavovat pouze jeden aktuální IDisposable prvek. Na tento prvek je Dispose() volán buď při výměně za jiný prvek nebo při volání Dispose() na celém objektu. Prvek se nastavuje kdykoliv ale až po založení objektu. Pokud je prvek měněn až po volaní Dispose() na objektu samotném, je na prvek rovnou voláno Dispose().
public sealed class MutableDisposable : IDisposable
{
    #region member varible and default property initialization
    private IDisposable current;
    private bool disposed;
    private object gate = new object();
    #endregion

    #region action methods
    public void Dispose()
    {
        lock (this.gate)
        {
            if (!this.disposed)
            {
                this.disposed = true;
                if (this.current != null)
                {
                    this.current.Dispose();
                    this.current = null;
                }
            }
        }
    }
    #endregion

    #region property getters/setters
    public IDisposable Disposable
    {
        get { return this.current; }
        set
        {
            bool disposed = false;
            lock (this.gate)
            {
                disposed = this.disposed;
                if (!disposed)
                {
                    if (this.current != null)
                    {
                        this.current.Dispose();
                    }
                    this.current = value;
                }
            }
            if (disposed && (value != null))
            {
                value.Dispose();
            }
        }
    }
    #endregion
}

Ještě asi dlužím alespoň nějaký jednoduchý příklad použití, co třeba tohle:

Používáme nějakého klienta, který má velmi pomalé ukončování (já jsem se s tímto problémem setkal u jednoho COM objektu). Co můžeme udělat je volat jeho dispose akci asynchronně bez zbytečného čekání. Pomoci třídy Disposable to provedeme takto:

public class ClientWithExpensiveDispose : IDisposable
{
    //

    public void Dispose()
    {
        //Expensive dispose
    }
}

void Main()
{
    var client = new ClientWithExpensiveDispose();

    using (Disposable.Create(() => Task.Factory.StartNew(client.Dispose)))
    {
        //Use client
    }

    //Other work
}

 

hodnocení článku

0       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback