読者です 読者をやめる 読者になる 読者になる

プログラマーな日々

プログラマーをやっています。好きなツール:WorkFlowy、好きな言語:C#、好きなサービス:Backlog、好きなAWS:AWS Lambda。

保持しているリソースのDisposeを呼び出さないTableAdapterの問題に対処する

.NET

TableAdapterって便利ですよね。でも大きな欠点が1つあります。それは内部で保持しているIDisposableを実装しているリソースのDisposeを呼び出さないことです。

そこで、その欠点に対処するヘルパーメソッドを書いてみました。

using System.Data.Common;
using System.Reflection;
using SystemExtensions;

namespace Models {
    /// <summary>
    /// <c>TableAdapter</c> をサポートする静的なメソッドを提供します。
    /// </summary>
    public static class TableAdapterHelper {
        /// <summary>
        /// 使用しているマネージ リソースを解放します。
        /// </summary>
        /// <param name="tableAdapter">マネージ リソースを解放する<c>TableAdapter</c></param>
        /// <param name="disposing">マネージ リソース解放する場合は true。</param>
        /// <remarks>
        /// <c>TableAdapter</c> が保持している <see cref="System.IDisposable"/> 実装リソースの 
        /// <see cref="System.IDisposable.Dispose"/> を呼び出さない不具合に対処します。
        /// </remarks>
        public static void Dispose(Component tableAdapter, bool disposing) {
            if(disposing) {
                Type t = tableAdapter.GetType();

                DbCommand[] commands = GetField(t, "_commandCollection").GetValue(tableAdapter) as DbCommand[];
                if(commands != null) {
                    foreach(DbCommand c in commands) {
                        c.NullSafeDispose();
                    }
                }

                FieldInfo f = GetField(t, "_adapter");
                if(f != null) {
                    DbDataAdapter adapter = f.GetValue(tableAdapter) as DbDataAdapter;

                    if(adapter != null) {
                        adapter.UpdateCommand.NullSafeDispose();
                        adapter.InsertCommand.NullSafeDispose();
                        adapter.DeleteCommand.NullSafeDispose();
                        adapter.SelectCommand.NullSafeDispose();
                        adapter.NullSafeDispose();
                    }
                }
            }
        }

        private static FieldInfo GetField(Type type, string fieldName) {
            FieldInfo fi = type.GetField(
                                fieldName,
                                BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
            return fi;
        }
    }
}

NullsafeDisposeについてはこちらを参照してください。
拡張メソッドでNullセーフなメソッドを実装する - プログラマーな日々
オブジェクトのNullチェックって面倒ですよね。.Net 3.0以降なら拡張メソッドでNullセーフなメソッドを実装できます。 例えば、IDisposableにNullチェックを入れたDisposeを実装するならこんな感じです。 ...

こんな風に使用します。

using Models;

public partial class TableAdapter {
    /// <summary>
    /// 使用しているアンマネージ リソースを解放し、オプションでマネージ リソースも解放します。
    /// </summary>
    /// <param name="disposing">
    /// マネージ リソースとアンマネージ リソースの両方を解放する場合は true。
    /// アンマネージ リソースだけを解放する場合は false。
    /// </param>
    protected override void Dispose(bool disposing) {
        TableAdapterHelper.Dispose(this, disposing);
        base.Dispose(disposing);
    }
}

それにしてもなぜMSの開発チームはTableAdapterをSystem.ComponentModel.Componentから直接派生させてしまったのでしょう?せめてTableAdapterBaseとかいうクラスを経由させてくれてたら、リフレクションなんて面倒なことをしなくてもすんだのに。