C#で引数をインターフェースで受けた時に実際の具象クラスがIDsiposabeを継承していてそれをDisposeしたいときの方法です。継承階層は以下の通りで青いIDatabseで変数を受けた時の話になります。
コードにすると以下のような状態です。
// コード例 public class IDatabse { void Operate(string command); } public class DbImple : IDatabse, IDisposable { public void Operate(string command) { /* Omitted */ } }
このような状態で以下のように処理を受けた際に、usingステートメントに放り込んで資源を解放しようとすると「エラー CS1674 'IFoo': using ステートメントで使用される型は、暗黙的に 'System.IDisposable' への変換が可能でなければなりません。」となりコンパイルエラーが発生します。
public void Main(params string[] args) { using(IDatabse db = Factory.CreateInstance(args[0])) // エラーが起きる { } }
通常、こういったケースが想定されるならIDatabseの宣言を
public interface IDatabase : IDisposable
のようにIDisposableを継承するようにしておく方がいいのですが、大抵リソース解放を継承クラスに強要することになりISP(インターフェイス分離の原則)に違反してしまいます。したがって、以下のようにコードを書いて、解放可能ならば解放するような処理を書きます。が、コレ、定型的な処理のため毎回書くのはちょっと面倒です。
public void Main(params string[] args) { IDatabse db = Factory.CreateInstance(args[0])); try { } finally { if(db is IDsposable dis) { using(dis) { /* nop */ } } } }
そこで、この問題の解決法をC#の機能を利用して2種類作成してみました。
該当箇所をUtility化する
staticクラスを作成して、該当部分をメソッド化して外に出します。
// Disposeする箇所を汎用処理にする public static class Context { public void Dispose(object source) { if(db is IDsposable dis) { using(dis) { /* nop */ } } } } public void Main(params string[] args) { IDatabse db = Factory.CreateInstance(args[0])); try { // 何らかの処理 } finally { ResourceUtil.Dispose(db); // 上記のメソッドを呼び出す } }
objectクラスへ拡張メソッドを追加する
objectクラスへ拡張メソッドとしてDisposeを追加します。
こっちの方が見かけはシンプルになります。Dispose内で安全性を確認しているので、取り合えず最後に呼び出すこともできます。
public static class ObjectExtension { public static void Dispose(this object obj) { if(obj is IDisposable dis) { using (dis) { } } } } public void Main(params string[] args) { IDatabse db = Factory.CreateInstance(args[0])); try { // 何らかの処理 } finally { db.Dispose(); // 拡張メソッドを呼び出す。 } }
基本クラスを拡張するのは一見いいアイデアですが、拡張しすぎて意味が分からなくなる可能性もあるので、ライフサイクルの長くなりそうなソフトではできる限りstaticクラスで処理することをおすすめします。