
このテンプレートは単純であるだけでなく、非常に単純であるように思われます。詳細は、複数の有名な本で説明されています。
それにもかかわらず、1つのプロジェクトのフレームワーク内であっても、自転車、松葉杖、漏れから動物園を作成するさまざまな方法で実装されることがよくあります。
自転車の発明を最小限に抑え、コードの量を最小限に抑え、表現力と透明性を高めることに基づいた実装方法を共有したいと思います。
前提条件
管理対象リソースと管理対象外リソースの混在なし
私は自分で実装したことはなく、同僚に管理されたリソースと管理されていないリソースの所有権を1つのクラスで使用することを勧めません。
したがって、1つのクラスは次のことができます。
- リソースをまったく所有しない
- 1つのアンマネージリソースを所有します。つまり、単にマネージリソースに変換します。
- 1つ以上の管理対象リソースを所有する
実装の継承は望ましくない
どうしても必要な場合を除き、クラスの継承は使用しません;提案された実装では、シールクラスのインスタンスをリソースのオブジェクト所有者と想定しています。
これは、継承をサポートするために変更できないという意味ではありません。
Janitor.Fodyを使用して実装されたアンマネージリソースのラッパー
更新:この目的のために、 CriticalFinalizerObjectとSafeHandleから継承されたクラスを使用する方が良いと、 コメントは正しく指摘しました 。
私が使用したもの
Fody用のこのプラグイン-コンパイル後にアセンブリコードを変更するための無料ツール-を使用すると、リソースの正しいリリースに必要な1,000の実装の詳細を手動で書き出すことができなくなります。
あなたのコード( ドキュメントの例):
後処理結果:
あなたのコード( ドキュメントの例):
public class Sample : IDisposable { IntPtr handle; public Sample() { handle = new IntPtr(); } public void Method() { //Some code } public void Dispose() { //must be empty } void DisposeUnmanaged() { CloseHandle(handle); handle = IntPtr.Zero; } [DllImport("kernel32.dll", SetLastError=true)] static extern bool CloseHandle(IntPtr hObject); }
後処理結果:
public class Sample : IDisposable { IntPtr handle; volatile int disposeSignaled; bool disposed; public Sample() { handle = new IntPtr(); } void DisposeUnmanaged() { CloseHandle(handle); handle = IntPtr.Zero; } [DllImport("kernel32.dll", SetLastError = true)] static extern Boolean CloseHandle(IntPtr handle); public void Method() { ThrowIfDisposed(); //Some code } void ThrowIfDisposed() { if (disposed) { throw new ObjectDisposedException("TemplateClass"); } } public void Dispose() { if (Interlocked.Exchange(ref disposeSignaled, 1) != 0) { return; } DisposeUnmanaged(); GC.SuppressFinalize(this); disposed = true; } ~Sample() { Dispose(); } }
これで、この記事が書かれた最も一般的なケースに移ることができます。
管理対象リソースの破棄デザインパターンの実装
準備する
まず、 Reactive ExtensionsライブラリのCompositeDisposableクラスが必要です。
小さな拡張メソッドを追加する必要があります。
public static void Add(this CompositeDisposable litetime, Action action) { lifetime.Add(Disposable.Create(action)); }
クリーンアップフィールドを追加する
private readonly CompositeDisposable lifetime = new CompositeDisposable();
Disposeメソッドの実装
public void Dispose() { lifetime.Dispose(); }
このメソッドにこれ以上何も追加する必要はありません。
明示的に構築されたリソースをクリアする
最も単純なコードをリソース割り当ての場所に直接追加するだけです。
それは:
myOwnResourceField = new Resource(); // - if (myOwnResourceField != null) { myOwnResourceField.Dispose(); myOwnResourceField = null; }
次のようになりました:
lifetime.Add(myOwnedResourceField = new Resource());
イベントの退会
それは:
sender.Event += Handler; // - sender.Event -= Handler
次のようになりました:
sender.Event += Handler; lifetime.Add(() => sender.Event -= Handler);
IObservableからのサブスクライブ解除
それは:
subscription = observable.Subscribe(Handler); // - if (subscription != null) { subscription.Dispose(); subscription = null; }
次のようになりました:
lifetime.Add(observable.Subscribe(Handler));
任意のクリーニングアクションの実行
CreateAction(); lifetime.Add(() => DisposeAction());
オブジェクトの状態を確認する
if (lifetime.IsDisposed)
結論
提案された方法:
- ユニバーサル:「クリーニング時に次のコードを実行する」など、すべての管理対象リソースをカバーすることが保証されています
- 表現力豊か:追加コードの量が少ない
- 使い慣れた:非常に人気のあるライブラリの通常のクラスが使用され、さらに必要に応じて独立して簡単に記述できます
- 透過的:各リソースのクリーニングコードはキャプチャコードの近くに配置され、レビューの際にほとんどの潜在的なリークがすぐに認識されます
- パフォーマンスの低下:新しいオブジェクトを作成して「メモリトラフィック」を追加します
- すでに「デッド」オブジェクトを使用するセキュリティには影響しません。自身のリソースは1回だけクリアされますが、ObjectDisposedExceptionのスローによるチェックは手動で行う必要があります。
記載された方法が読者に役立つなら、私はうれしいです。