C# - IDisposable

개요

  • Disposable.Create() 로 IDisposable을 모을 수 있다.
  • 컨테이너(IDisposableContainer)를 만들고 IDisposable한 인터페이스를 .AddTo(container)로 등록한다.
public sealed class Resource : IDisposable {
    private readonly string name;
    public Resource( string name ) {
        this.name = name;
    }
    public void Dispose() => Console.WriteLine( "Disposed." + this.name );
}

static void Main( string[] args ) {
    // IDisposable한 Action 컨테이너를 만든다.
    IDisposableContainer container = Disposable.Create();

    // 임의의 리소스를 만들고, 컨테이너에 저장
    var resource = new Resource( "hoge" );
    var removeToken = resource.AddTo( container );

    removeToken.Dispose();  // resource.Dispose()를 실행하고, container에서 resource를 해제한다.

    // removeToken가 필요 없는 경우…
    Resource resource2 = new Resource().AddToChain( container );

    // container에 저장된 IDisposable한 Action울 모아서 해방한다.
    container.Dispose();
}

사용 방법

  • IDisposable한 오브젝트는 확장 메소드에 의해서 AddTo, AddToChain 메소드를 사용할 수 있으므로 AddTo, AddToChain에 등록처의 IDisposableContainer를 넘긴다.
    • AddTo : 컨테이너에서 개별적으로 해방할 수 있는 IDisposable을 취득한다.
    • AddToChain : 컨테이너에 오브젝트를 등록하고 그대로 반환한다.

DisposableToken

  • 외부에서 리소스 등록만 허가하고 싶은 경우가 있다.
  • DisposableToken을 공개하는 것으로 IDisposable.Dispose()을 외부에 비공개로 할 수 있다.
IDisposableContainer container = Disposable.Create();
DisposableToken token = container.Token;
  • token을 AddTo, AddToChain 메소드에 넘길 수 있다.

CancellationToken으로서 이용

  • DisposableToken은 CancellationToken으로 암묵적으로 캐스트 할 수 있다. 그래서 Rx의 Subscribe하거나 Task의 캔슬토큰으로 이용할 수 있다.
//DisposableToken을 CancellationToken으로서 이용한다
IDisposableContainer container = Disposable.Create();
DisposableToken token = container.Token;
Observable.Interval( TimeSpan.FromSeconds( 1 ) )
    .Subscribe( Console.WriteLine , token );

container.Dispose()가 호출 될 때 취소된다.

파이널리자 이용

  • .NET에는 불요하게된 오브젝트를 회수하는 GC 기능이 있다.
  • 파이널리자가 정의된 오브젝트는 회수 되기 직전에 어떤 처리를 실행할 수 있다.
// 파이널리즈를 가능화 한다
IDisposableContainer container = Disposable.Create().ToFinalizable();
  • 이와 같이 ToFinalizable()를 호출하면 파이널리자로서 container.Dispose()가 호출되도록 변환할 수 있다.

약 참조에 의한 관리

  • 파이널리자를 정의한 오브젝트는 보통의 오브젝트에 비해서 회수 되기까지의 시간이 걸린다.
IDisposableContainer container = Disposable.Create().ToWeak();
  • ToWeak()를 호출하면 파이널리자에 속박되지 않고 container의 참조 무효화를 검출하고, Dispose() 할 수 있도록 한다.

동기 컨텍스트 이용

  • UI 스레드(STA)에서 조작하지 않으면 안되는 것이 있다.
  • ToWeak로 하고 ToFinalizable로 하고 UI 스레드와는 서로 다른 스레드 상에서 동작하기 때문에 UI 스레드에 돌아와서 Dispose해야할 필요가 있다.
IDisposableContainer container =
        Disposable.Create().ToSynchronizable( SynchronizationContext.Current );
  • ToSynchronizable로 동기 컨텍스트를 지정할 수 있다.
  • SynchronizationContext에는 Send(동기)、Post(비동기)가 있지만 Post가 이용된다.

조합

  • container의 참조가 무효화 되었다면 동기 컨텍스트에서 동기하여 Dispose 되도록 한다.
IDisposableContainer container = Disposable.Create()
   .ToSynchronizable( SynchronizationContext.Current )
   .ToWeak();

코드

  • 스레드 세이프
  • 복 수회 Dispose를 호출해도 문제 없다.
  • Dispose 후에 리소스가 등록되면 부활한다.(다시 파이널리즈 가능이 된다)
  • C# 6.0, VS2015 필요
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
namespace PITA {
    /// <summary>
    /// 복수의 리소스 및 델리게이트를 집약 관리하는 컨테이너를 뜻한다
    /// </summary>
    public interface IDisposableContainer : IDisposable {
        /// <summary>
        /// 오브젝트를 컨테이너에 등록한다.
        /// </summary>
        /// <typeparam name="T">등록될 오브제그트의 타입</typeparam>
        /// <param name="resource">컨테이너에 등록되는 오브젝트</param>
        /// <returns>컨테이너에 등록된 오브젝트</returns>
        T Register<T>( T resource ) where T : IDisposable;

        /// <summary>
        /// <see cref="Action"/>을 리소스로서 컨테이너에 등록한다
        /// </summary>
        /// <param name="action">컨테이너에 등록하는 델리게이트</param>
        void Register( Action action );

        /// <summary>
        /// (해제 가능) 오브젝트를 컨테이너에 등록한다.
        /// </summary>
        /// <typeparam name="T">등록 되는 오브젝트의 타입</typeparam>
        /// <param name="resource">컨테이너에 등록 되는 오브젝트</param>
        /// <returns>コンテナから<paramref name="resource"/>을 해방하고,<paramref name="resource"/>の<see cref="IDisposable.Dispose"/>의 실행에 이용할 수 있다<see cref="IDisposable"/></returns>
        RemoveableDisposableToken RemoveableRegister<T>( T resource ) where T : IDisposable;

        /// <summary>
        /// (해제 가능) <see cref="Action"/>을 컨테이너에 등록한다
        /// </summary>
        /// <param name="action">컨테이너에 등록 되는 델리게이트</param>
        /// <returns>컨테이너에서<paramref name="action"/>을 해방하고, <paramref name="action"/>의 실행에 이용 할 수 있다<see cref="IDisposable"/></returns>
        RemoveableDisposableToken RemoveableRegister( Action action );

        /// <summary>
        /// 컨테이너에 관련된 토큰을 취득한다
        /// 이 토큰은 <see cref="CancellationToken"/>로 행동 할 수 있다
        /// </summary>
        DisposableToken Token {
            get;
        }
    }

    /// <summary>
    /// 범용 가능한<see cref="IDisposable"/>을 구축하기 위한 기저 클래스를 뜻한다
    /// </summary>
    public abstract class AnonymousDisposableBase : IDisposableContainer {

        /// <summary>
        /// 何もしない<see cref="IDisposable"/>を取得します。
        /// </summary>
        public static IDisposable Empty => Disposable.Empty;

        private int disposed = 0;
        private Action disposes = null;
        private CancellationTokenSource _cancelSource = null;

        /// <summary>
        /// リソース追加します。
        /// </summary>
        public T Register<T>( T resource ) where T : IDisposable {
            this.Register( resource.Dispose );
            return resource;
        }

        /// <summary>
        /// デリゲートをリソースとして登録します。
        /// </summary>
        /// <param name="action">リソースとして登録されるデリゲート</param>
        public void Register( Action action ) {
            if( Interlocked.CompareExchange( ref this.disposed , 0 , 1 ) == 1 )
                GC.ReRegisterForFinalize( this );
            this.disposes += action;
        }

        /// <summary>
        /// (解除可能) リソースを登録します。
        /// </summary>
        /// <typeparam name="T">登録されるオブジェクトの型</typeparam>
        /// <param name="resource">リソースとして登録されるオブジェクト</param>
        /// <returns>リソースの解除およびリソース<see cref="IDisposable.Dispose"/>の実行に使用できるコンテナ</returns>
        public RemoveableDisposableToken RemoveableRegister<T>( T resource ) where T : IDisposable => this.RemoveableRegister( resource.Dispose );

        /// <summary>
        /// (解除可能) <see cref="Action"/>を登録します。
        /// </summary>
        /// <param name="action">リソースとして登録される<see cref="Action"/></param>
        /// <returns>解除および登録された<see cref="Action"/>の実行に使用できる<see cref="IDisposable"/></returns>
        public RemoveableDisposableToken RemoveableRegister( Action action ) {
            this.Register( action );
            return new RemoveableDisposableToken( action , this.removeAction );
        }

        private void removeAction( Action action ) => this.disposes -= action;

        /// <summary>
        /// このオブジェクトに関連する<see cref="CancellationToken"/>を取得します。
        /// </summary>
        public CancellationToken CancelToken {
            get {
                if( Interlocked.CompareExchange( ref this.disposed , 0 , 1 ) == 1 )
                    GC.ReRegisterForFinalize( this );
                return LazyInitializer.EnsureInitialized( ref this._cancelSource ).Token;
            }
        }

        /// <summary>
        /// 登録操作のみに限定したトークンを取得します。
        /// </summary>
        public DisposableToken Token => new DisposableToken( this );

        /// <summary>
        /// 登録されている<see cref="IDisposable.Dispose"/>または、<see cref="Action"/>を呼び出し、それらのリソースを解放します。
        /// </summary>
        protected void OnDispose() {
            Interlocked.CompareExchange( ref this._cancelSource , null , this._cancelSource )?.Cancel();
            Interlocked.CompareExchange( ref this.disposes , null , this.disposes )?.Invoke();

            if( Interlocked.CompareExchange( ref this.disposed , 1 , 0 ) == 0 )
                GC.SuppressFinalize( this );
        }

        /// <summary>
        /// 登録されている<see cref="IDisposable.Dispose"/>または、<see cref="Action"/>を呼び出し、それらのリソースを解放します。
        /// </summary>
        public virtual void Dispose() => this.OnDispose();

        /// <summary>
        /// このオブジェクトのキャンセルトークンを取得します。
        /// </summary>
        /// <param name="source"></param>
        public static implicit operator CancellationToken( AnonymousDisposableBase source ) => source.CancelToken;
    }

    /// <summary>
    /// 汎用的な使用を目的とする<see cref="IDisposable"/>を提供します。
    /// </summary>
    public sealed class AnonymousDisposable : AnonymousDisposableBase {
        /// <summary>
        /// <see cref="AnonymousDisposable"/>クラスの新しいインスタンスを初期化します。
        /// </summary>
        internal AnonymousDisposable() {
            return;
        }

        /// <summary>
        /// <see cref="AnonymousDisposable"/>クラスの新しいインスタンスを初期化します。
        /// </summary>
        /// <param name="action">このオブジェクトの<see cref="IDisposable.Dispose"/>が実行されたとき呼び出す必要のある<see cref="Action"/></param>
        internal AnonymousDisposable( Action action ) {
            this.Register( action );
        }
    }

    /// <summary>
    /// <see cref="SynchronizationContext"/>を使用して、<see cref="IDisposable.Dispose"/>を呼び出します。
    /// </summary>
    public sealed class ContextDisposable : AnonymousDisposableBase {
        private readonly SynchronizationContext context;
        /// <summary>
        /// 現在の<see cref="SynchronizationContext"/>を指定して、<see cref="ContextDisposable"/>クラスの新しいインスタンスを初期化します。
        /// </summary>
        /// <param name="resource"><see cref="SynchronizationContext"/>に同期して<see cref="IDisposable.Dispose"/>を呼び出す必要のあるオブジェクト</param>
        internal ContextDisposable( IDisposable resource ) : this( SynchronizationContext.Current , resource ) {
            return;
        }

        /// <summary>
        /// <see cref="SynchronizationContext"/>を指定して、<see cref="ContextDisposable"/>クラスの新しいインスタンスを初期化します。
        /// </summary>
        /// <param name="context">同期コンテキスト</param>
        /// <param name="resource"><see cref="SynchronizationContext"/>に同期して<see cref="IDisposable.Dispose"/>を呼び出す必要のあるオブジェクト</param>
        internal ContextDisposable( SynchronizationContext context , IDisposable resource ) {
            this.context = context;
            this.Register( resource );
        }

        /// <summary>
        /// コンストラクタで指定された<see cref="SynchronizationContext"/>に同期して、<see cref="IDisposable.Dispose"/>の実行を試みます。
        /// </summary>
        public override void Dispose() {
            if( this.context != null ) {
                if( this.context == SynchronizationContext.Current ) {
                    this.OnDispose();
                } else {
                    this.context.OperationStarted();
                    this.context.Post( _ => {
                        try {
                            this.OnDispose();
                        } finally {
                            this.context.OperationCompleted();
                        }
                    } , null );
                }
            } else {
                base.Dispose();
            }
        }
    }

    /// <summary>
    /// <see cref="IDisposable.Dispose"/>をファイナライザで実行できるようにカプセル化します
    /// </summary>
    public sealed class AnonymousFinalizable : AnonymousDisposableBase {
        /// <summary>
        /// <see cref="AnonymousFinalizable"/>クラスの新しいインスタンスを初期化します。
        /// </summary>
        /// <param name="resource">このオブジェクトがファイナライズされると、関連して<see cref="IDisposable.Dispose"/>を実行するオブジェクト</param>
        internal AnonymousFinalizable( IDisposable resource ) {
            if( resource == null )
                throw new ArgumentNullException( nameof(resource) );
            this.Register( resource );
        }

        /// <summary>
        /// ファイナライザ
        /// </summary>
        ~AnonymousFinalizable() {
            this.Dispose();
        }
    }

    /// <summary>
    /// リソースの登録操作に限定します。
    /// また、<see cref="CancellationToken"/>としての振る舞いを提供します。
    /// </summary>
    public struct DisposableToken {
        private readonly IDisposableContainer source;

        /// <summary>
        /// トークンに関連付けられる<see cref="IDisposableContainer"/>を指定して<see cref="DisposableToken"/>を初期化します。
        /// </summary>
        /// <param name="resource">ホストされる<see cref="AnonymousDisposableBase"/></param>
        public DisposableToken( IDisposableContainer resource ) {
            this.source = resource;
        }

        /// <summary>
        /// リソースを登録します。
        /// </summary>
        /// <typeparam name="T">登録されるリソースの型</typeparam>
        /// <param name="resource">登録されるリソース</param>
        /// <returns>登録されたリソース</returns>
        public T Register<T>( T resource ) where T : IDisposable => this.source.Register( resource );

        /// <summary>
        /// <see cref="Action"/>をリソースとして登録します。
        /// </summary>
        /// <param name="action">リソースとして登録される<see cref="Action"/></param>
        public void Register( Action action ) => this.source.Register( action );

        /// <summary>
        /// (解除可能) <see cref="Action"/>を登録します。
        /// </summary>
        /// <param name="action">リソースとして登録される<see cref="Action"/></param>
        /// <returns>解除および登録された<see cref="Action"/>の実行に使用できる<see cref="IDisposable"/></returns>
        public RemoveableDisposableToken RemoveableRegister( Action action ) => this.source.RemoveableRegister( action );

        /// <summary>
        /// (解除可能) リソースを登録します。
        /// </summary>
        /// <typeparam name="T">登録されるオブジェクトの型</typeparam>
        /// <param name="resource">リソースとして登録されるオブジェクト</param>
        /// <returns>リソースの解除およびリソース<see cref="IDisposable.Dispose"/>の実行に使用できるコンテナ</returns>
        public RemoveableDisposableToken RemoveableRegister<T>( T resource ) where T : IDisposable => this.source.RemoveableRegister( resource );

        /// <summary>
        /// キャンセルトークンを取得します。
        /// </summary>
        public static implicit operator CancellationToken( DisposableToken token ) => ( (AnonymousDisposableBase)token.source ).CancelToken;
    }

    /// <summary>
    /// 弱い参照として<see cref="IDisposable"/>を管理します。
    /// トークンの参照が維持されなくなると、自動的に<see cref="IDisposable.Dispose"/>)を実行し、リソースを解放できます。
    /// </summary>
    internal static class WeakDisposableManager {
        private static long containerSeed = 0;
        private static int cleanuping = 0;
        private static int autoCleanupRunning = 0;
        private readonly static ConcurrentDictionary<long , WeakDisposable> weakObjects = new ConcurrentDictionary<long , WeakDisposable>();

        /// <summary>
        /// 定期的に参照の無効なトークンを検出し、それらのリソースを解放します。
        /// </summary>
        private async static void autoCleanupAsync() {
            if( Interlocked.CompareExchange( ref autoCleanupRunning , 1 , 0 ) == 0 ) {
                var oldCount = gcCollectCount();
                try {
                    while( weakObjects.Count > 0 && !Environment.HasShutdownStarted ) {
                        await Task.Delay( TimeSpan.FromMinutes( 1 ) ).ConfigureAwait( false );
                        var gc = gcCollectCount();
                        if( gc != oldCount ) {
                            oldCount = gc;
                            Cleanup();
                        }
                    }
                } finally {
                    Interlocked.Exchange( ref autoCleanupRunning , 0 );
                }
            }
        }

        /// <summary>
        /// ジェネレーション 1 以上で発生したガベージコレクトの発生回数を取得します。
        /// </summary>
        /// <returns>ジェネレーション 1 以上で発生したガベージコレクトの発生回数</returns>
        private static int gcCollectCount() {
            int count = 0;
            for( int i = 1; i <= GC.MaxGeneration; i++ )
                count += GC.CollectionCount( i );
            return count;
        }

        /// <summary>
        /// 参照が無効なトークンを検出し、それらのリソースを解放します。
        /// </summary>
        public static void Cleanup() {
            if( weakObjects.Count > 0 && Interlocked.CompareExchange( ref cleanuping , 1 , 0 ) == 0 ) {
                try {
                    foreach( var x in weakObjects ) {
                        if( x.Value.HasInstance )
                            continue;
                        x.Value.Dispose();
                    }
                } finally {
                    Interlocked.Exchange( ref cleanuping , 0 );
                }
            }
        }

        /// <summary>
        /// トークンの参照が解除されたときに実行される<see cref="Action"/>を指定して、トークンを生成します。
        /// </summary>
        /// <param name="action">トークンの参照が解除されたときに実行される<see cref="Action"/></param>
        /// <returns>このトークンオブジェクトの参照が保持されなくなると、<paramref name="action"/>を実行します。</returns>
        public static IDisposableContainer Create( Action action ) {
            if( action == null )
                throw new ArgumentNullException( nameof(action) );
            return new WeakDisposable( action ).WeakToken;
        }

        /// <summary>
        /// トークンの参照が解除されたときに解放される<see cref="IDisposable"/>を指定して、トークンを生成します。
        /// </summary>
        /// <param name="resource">トークンの参照が解除されたときに解放される<see cref="IDisposable"/></param>
        /// <returns>このトークンオブジェクトの参照が保持されなくなると、<paramref name="resource"/>を解放します。</returns>
        public static IDisposableContainer Create( IDisposable resource ) {
            if( resource == null )
                throw new ArgumentNullException( nameof(resource) );
            return Create( resource.Dispose );
        }

        /// <summary>
        /// 参照の維持に使用するトークンを表します。
        /// </summary>
        public sealed class WeakToken : IDisposableContainer {
            private readonly WeakDisposable host;

            internal WeakToken( WeakDisposable host ) {
                this.host = host;
            }

            public DisposableToken Token => this.host.Token;
            public void Register( Action action ) => this.host.Register( action );
            public T Register<T>( T resource ) where T : IDisposable => this.host.Register( resource );
            public RemoveableDisposableToken RemoveableRegister( Action action ) => this.host.RemoveableRegister( action );
            public RemoveableDisposableToken RemoveableRegister<T>( T resource ) where T : IDisposable => this.host.RemoveableRegister( resource );
            public void Dispose() => this.host.Dispose();
        }

        internal sealed class WeakDisposable : AnonymousDisposableBase {
            private readonly WeakReference<WeakToken> weakToken;
            private readonly long seed;

            public WeakDisposable() {
                this.seed = Interlocked.Decrement( ref containerSeed );
                weakObjects.TryAdd( seed , this );
                this.weakToken = new WeakReference<WeakToken>( new WeakToken( this ) );
                autoCleanupAsync();
            }

            /// <summary>
            /// <see cref="WeakDisposable"/>クラスの新しいインスタンスを初期化します。
            /// </summary>
            /// <param name="action">トークンの参照が無効になると、実行される<see cref="Action"/></param>
            public WeakDisposable( Action action ) : this() {
                if( action == null )
                    throw new ArgumentNullException( nameof(action) );
                this.Register( action );
            }

            /// <summary>
            /// 参照の保持に使用するトークンオブジェクトを取得します。
            /// </summary>
            public WeakToken WeakToken {
                get {
                    WeakToken tmp;
                    if( this.weakToken.TryGetTarget( out tmp ) )
                        return tmp;
                    throw new InvalidOperationException( nameof(WeakToken) );
                }
            }

            /// <summary>
            /// トークンの参照が保持されているかどうか取得します。
            /// </summary>
            public bool HasInstance {
                get {
                    WeakToken tmp;
                    return this.weakToken.TryGetTarget( out tmp ) && tmp != null;
                }
            }

            /// <summary>
            /// コンストラクタによって関連付けられた<see cref="Action"/>を実行し、それらのリソースを解放します。
            /// </summary>
            public override void Dispose() {
                this.OnDispose();
                WeakDisposable tmp = null;
                weakObjects.TryRemove( this.seed , out tmp );
            }
        }
    }

    /// <summary>
    /// <see cref="IDisposableContainer"/>の作成および、<see cref="IDisposable"/>の拡張メソッドを提供します。
    /// </summary>
    public static class Disposable {
        /// <summary>
        /// ファイナライザが<paramref name="source"/>の<see cref="IDisposable.Dispose"/>を実行できるように変換します。
        /// </summary>
        /// <param name="source">ファイナライザで<see cref="IDisposable.Dispose"/>を実行するオブジェクト</param>
        public static IDisposableContainer ToFinalizable<T>( this T source ) where T : class, IDisposable =>
            source is AnonymousFinalizable ? source as AnonymousFinalizable : new AnonymousFinalizable( source );

        /// <summary>
        /// 現在の<see cref="SynchronizationContext"/>に同期して<paramref name="source"/>の<see cref="IDisposable.Dispose"/>を実行できるように変換します。
        /// </summary>
        /// <param name="source">現在の<see cref="SynchronizationContext"/>に同期して<see cref="IDisposable.Dispose"/>を実行するオブジェクト</param>
        public static IDisposableContainer ToSynchronizable( this IDisposable source )
            => source is ContextDisposable ? (ContextDisposable)source : new ContextDisposable( source );

        /// <summary>
        /// <paramref name="context"/>によって指定された<see cref="SynchronizationContext"/>に同期して、<paramref name="source"/>の
        /// <see cref="IDisposable.Dispose"/>を実行できるように変換します。
        /// </summary>
        /// <param name="source"><paramref name="context"/>によって指定された<see cref="SynchronizationContext"/>に同期して<see cref="IDisposable.Dispose"/>を実行するオブジェクト</param>
        /// <param name="context">同期コンテキスト</param>
        public static IDisposableContainer ToSynchronizable( this IDisposable source , SynchronizationContext context )
            => source is ContextDisposable ? (ContextDisposable)source : new ContextDisposable( context , source );

        /// <summary>
        /// 参照が維持されなくなると、<paramref name="source"/>の<see cref="IDisposable.Dispose"/>を実行します。
        /// </summary>
        /// <param name="source">参照が維持されていない場合に解放する必要のある<see cref="IDisposable"/></param>
        /// <returns>参照の維持に使用するトークン</returns>
        public static IDisposableContainer ToWeak( this IDisposable source )
            => source is WeakDisposableManager.WeakToken ? (IDisposableContainer)source : WeakDisposableManager.Create( source );

        /// <summary>
        /// 指定の<see cref="Action"/>を連結した<see cref="IDisposable"/>を作成します。
        /// </summary>
        /// <param name="source">1番目のリソース</param>
        /// <param name="action">リソースとして振る舞う<see cref="Action"/></param>
        /// <returns><paramref name="source"/>と<paramref name="action"/>を連結した<see cref="IDisposable"/></returns>
        public static IDisposableContainer Concat( this IDisposable source , Action action ) {
            if( source == null )
                throw new ArgumentNullException( nameof(source) );
            if( action == null )
                throw new ArgumentNullException( nameof(action) );

            var container = source as IDisposableContainer;
            if( container != null ) {
                container.Register( action );
                return container;
            } else {
                return new AnonymousDisposable( source.Dispose + action );
            }
        }

        /// <summary>
        /// 2つの<see cref="IDisposable"/>を連結した<see cref="IDisposable"/>を作成します。
        /// </summary>
        /// <param name="source">1番目のオブジェクト</param>
        /// <param name="resource">2番目のオブジェクト</param>
        /// <returns>新しい<see cref="IDisposable"/></returns>
        public static IDisposableContainer Concat( this IDisposable source , IDisposable resource ) => Concat( source , resource.Dispose );

        /// <summary>
        /// (解除可能) <paramref name="resource"/>を<paramref name="container"/>に登録します。
        /// </summary>
        /// <typeparam name="T">オブジェクトの型</typeparam>
        /// <param name="resource"><paramref name="container"/>に登録されるリソース</param>
        /// <param name="container"><paramref name="resource"/>が登録される対象</param>
        /// <returns>リソース(<paramref name="resource"/>)の登録解除および解放に使用できるトークン</returns>
        public static RemoveableDisposableToken AddTo<T>( this T resource , IDisposableContainer container ) where T : IDisposable {
            if( resource == null )
                throw new ArgumentNullException( nameof(resource) );
            if( container == null )
                throw new ArgumentNullException( nameof(container) );
            return container.RemoveableRegister( resource );
        }

        /// <summary>
        /// (解除可能) <paramref name="token"/>がキャンセルされると、<paramref name="resource"/>の<see cref="IDisposable.Dispose"/>を実行します。
        /// </summary>
        /// <param name="resource"><paramref name="token"/>がキャンセルされると、<see cref="IDisposable.Dispose"/>を実行する必要のあるオブジェクト</param>
        /// <param name="token">トークンがキャンセルされると、<paramref name="resource"/>を解放します。</param>
        /// <returns>リソース(<paramref name="resource"/>)の登録解除および解放に使用できるトークン</returns>
        public static RemoveableDisposableToken AddTo<T>( this T resource , CancellationToken token ) where T : IDisposable {
            if( resource == null )
                throw new ArgumentNullException( nameof(resource) );
            return new RemoveableDisposableToken( token.Register( resource.Dispose ).Dispose + (Action)resource.Dispose );
        }

        /// <summary>
        /// (解除可能) <see cref="DisposableToken"/>を指定して、リソースを登録します。
        /// </summary>
        /// <param name="resource"><paramref name="token"/>に登録されるオブジェクト</param>
        /// <param name="token"><paramref name="resource"/>の登録対象</param>
        /// <returns>リソース(<paramref name="resource"/>)の登録解除および解放に使用できるトークン</returns>
        public static RemoveableDisposableToken AddTo<T>( this T resource , DisposableToken token ) where T : IDisposable {
            if( resource == null )
                throw new ArgumentNullException( nameof(resource) );
            return token.RemoveableRegister( resource );
        }

        /// <summary>
        /// <paramref name="resource"/>を<paramref name="container"/>に登録します。
        /// </summary>
        /// <typeparam name="T">オブジェクトの型</typeparam>
        /// <param name="resource"><paramref name="container"/>に登録されるリソース</param>
        /// <param name="container"><paramref name="resource"/>が登録される対象</param>
        /// <returns>コンテナに登録されたオブジェクト。<paramref name="resource"/></returns>
        public static T AddToChain<T>( this T resource , IDisposableContainer container ) where T : IDisposable {
            if( resource == null )
                throw new ArgumentNullException( nameof(resource) );
            if( container == null )
                throw new ArgumentNullException( nameof(container) );
            return container.Register( resource );
        }

        /// <summary>
        /// <paramref name="resource"/>を<see cref="CancellationToken"/>に登録します。
        /// </summary>
        /// <param name="resource"><paramref name="token"/>がキャンセルされると、<see cref="IDisposable.Dispose"/>を実行する必要のあるオブジェクト</param>
        /// <param name="token"><paramref name="resource"/>の<see cref="IDisposable.Dispose"/>の実行がトリガーされるトークン</param>
        /// <returns>トークンに登録されたオブジェクト。<paramref name="resource"/></returns>
        public static T AddToChain<T>( this T resource , CancellationToken token ) where T : IDisposable {
            if( resource == null )
                throw new ArgumentNullException( nameof(resource) );
            token.Register( resource.Dispose );
            return resource;
        }

        /// <summary>
        /// トークンにリソースを登録します。
        /// </summary>
        /// <param name="resource"><paramref name="token"/>に登録されるオブジェクト</param>
        /// <param name="token"><paramref name="resource"/>の登録対象</param>
        /// <returns>トークンに登録されたオブジェクト。<paramref name="resource"/></returns>
        public static T AddToChain<T>( this T resource , DisposableToken token ) where T : IDisposable {
            if( resource == null )
                throw new ArgumentNullException( nameof(resource) );
            return token.Register( resource );
        }

        /// <summary>
        /// リソースを集約管理できるコンテナを生成します。
        /// </summary>
        /// <returns>リソースを集約管理できるコンテナ</returns>
        public static IDisposableContainer Create() => new AnonymousDisposable();

        /// <summary>
        /// リソースを集約管理できるコンテナを生成します。
        /// </summary>
        /// <param name="action">コンテナに登録するデリゲート</param>
        /// <returns>リソースを集約管理できるコンテナ</returns>
        public static IDisposableContainer Create( Action action ) => new AnonymousDisposable( action );

        /// <summary>
        /// リソースを集約管理できるコンテナを生成します。
        /// </summary>
        /// <param name="resource">コンテナに登録するリソース</param>
        /// <returns>リソースを集約管理できるコンテナ</returns>
        public static IDisposableContainer Create<T>( T resource ) where T : IDisposable => new AnonymousDisposable( resource.Dispose );

        /// <summary>
        /// 何もしない<see cref="IDisposable"/>を取得します。
        /// </summary>
        public static IDisposable Empty {
            get;
        }
        = new EmptyDisposable();

        /// <summary>
        /// 参照が無効化されたトークンをクリーンアップします。
        /// </summary>
        public static void WeakDisposableCleanup() => WeakDisposableManager.Cleanup();

        /// <summary>
        /// 何もしない<see cref="IDisposable"/>を表します。
        /// </summary>
        private sealed class EmptyDisposable : IDisposable {
            public void Dispose() {
                return;
            }
        }

    }

    /// <summary>
    /// <see cref="IDisposableContainer"/>に登録されたリソースを回収し、そのリソースの解放に使用されるトークンを表します。
    /// </summary>
    public struct RemoveableDisposableToken : IDisposable {
        private readonly Action<Action> remove;
        private Action dispose;

        /// <summary>
        /// <see cref="IDisposable.Dispose"/>に関連付ける<see cref="Action"/>を指定して、<see cref="RemoveableDisposableToken"/>を初期化します。
        /// </summary>
        /// <param name="action"><see cref="IDisposable.Dispose"/>に関連付ける<see cref="Action"/></param>
        /// <param name="remove"><paramref name="action"/>を取り消すために使用するデリゲート</param>
        internal RemoveableDisposableToken( Action action , Action<Action> remove ) {
            if( action == null )
                throw new ArgumentNullException( nameof(action) );
            this.dispose = action;
            this.remove = remove;
        }

        /// <summary>
        /// <see cref="IDisposable.Dispose"/>に関連付ける<see cref="Action"/>を指定して、<see cref="RemoveableDisposableToken"/>を初期化します。
        /// </summary>
        /// <param name="action"><see cref="IDisposable.Dispose"/>に関連付ける<see cref="Action"/></param>
        internal RemoveableDisposableToken( Action action ) {
            if( action == null )
                throw new ArgumentNullException( nameof(action) );
            this.dispose = action;
            this.remove = null;
        }

        /// <summary>
        /// コンストラクタで指定された<see cref="Action"/>を実行します。
        /// </summary>
        public void Dispose() {
            if( this.remove != null )
                this.remove( this.dispose );
            Interlocked.CompareExchange( ref this.dispose , null , this.dispose )?.Invoke();
        }

        public override int GetHashCode() => ( this.dispose?.GetHashCode() ).GetValueOrDefault( 0 );
        public override bool Equals( object obj ) => obj is RemoveableDisposableToken && this.dispose == ( (RemoveableDisposableToken)obj ).dispose;
        public static bool operator ==( RemoveableDisposableToken x , RemoveableDisposableToken y ) => x.GetHashCode() == y.GetHashCode();
        public static bool operator !=( RemoveableDisposableToken x , RemoveableDisposableToken y ) => x.GetHashCode() != y.GetHashCode();
    }

}

참고

  • http://qiita.com/Temarin_PITA/items/43396f85890c5acb5b30

이 글은 2019-03-21에 작성되었습니다.