Linqのメソッドを追加してみる

提供: とある社畜の頭脳整理
移動先: 案内検索

プログラムを組んでいてLinqのメソッドを追加したいなぁなんて思うことがあると思うんだ。僕が最初に思ったのがデータテーブルから特定のレコードをいっぺんに削除したいときだったんだ。それ以外にも、標準偏差をメソッドひとつで…とか…。そこで、Linqのメソッドを追加してみようと思うんだ。

Linqのメソッドのありか

Linqにメソッドを追加するにも、まずはLinqのメソッドがどのクラスに定義されているかがわからないと追加できないんだ。実はLinqのメソッドは「IEnumerable<T>」にあるんだよ。ここまでわかれば、「標準のクラスにメソッドを追加する」を参考にメソッドを追加すればいいんだよ。

まずはクラスの追加

「標準のクラスにメソッドを追加する」を参考にクラスを追加するよ。

public static class IEnumerableEx
{

}

今回は「IEnumerable<T>」に追加するのでクラス名は「IEnumerableEx」にしたよ。

メソッドの追加

いろいろなHPに掲載されている「ForEach」を追加してみるよ。でもちょっとややこしいんだ。そもそも、IEnumerableを実装している配列って、何のクラスなのかわからないんだ。だから、不特定のクラスで動作できるようにするために、ジェネリックメソッドとして定義するんだ。
ジェネリックメソッドっていきなり出てきたけど…C#をコーディングしていると時々「<T>」ってやつに出くわすと思うんだ。(IEnumerableもIEnumerable<T>ってなってるよね。…厳密に言うとこれはジェネリッククラスだけど…)これを利用するんだよ。コーディングするときは「T」を型のようにしてコーディングするんだよ。

public static class IEnumerableEx
{
    public static void ForEach<T>(this IEnumerable<T> source)
    {
    }
}

返却する値がないので「void型」でForEachの要素の型が不明なので「<T>」を使って…引数「source」には「IEnumerable」自身が渡されるんだよね。

でも、これだと<T>型の何のメソッドを実行するかわからないから…ここで、デリゲートが登場するんだよ。

デリゲートの簡単な説明

デリゲートは簡単に言うと、メソッドを格納できる変数みたいなものなんだ。C言語やC++言語をやったことある人は、関数ポインタに似たものっていったほうがわかりやすいかもしれないね。実はこのデリゲートってイベントハンドラを使うと頻繁に使っているものなんだよ。
たとえば…プログレスバーを使うときにBackgroundWorkerって使うと思うけど、DoWorkEventHandlerにワーカースレッドを登録すると思うんだ。この「DoWorkEventHandler」はデリゲートのひとつなんだよ。(「WPFでプログレスバーを使ってみる」を参照)

細かい話はほかのHPに譲って(笑)前出の関数に引数を追加するよ。

public static class IEnumerableEx
{
    public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
    {
    }
}

「Action<T>」なんてものが出てきたけど、これは標準で提供されているデリゲートの1つなんだよ。メソッドからの戻り値が必要ない場合は「Action」、戻り値が必要な場合は「Func」を使うくらいに覚えておけばいいと思うんだ。

これで、T型(クラス)のどのメソッドを呼べばいいのかが関数に渡されたことになるんだ。

処理の実装

さて…T型の配列とどのメソッドを呼ぶのかがわかったので、処理を実装するよ。細か説明はコードの後でするね。

public static class IEnumerableEx
{
    public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
    {
        foreach (T item in source)
        {
            action(item);
        }
    }
}

引数「source」はT型の配列ってことだから…「foreach」で1つひとつの要素にアクセスしているんだ。引数「action」はどのメソッドを呼び出せば良いのか知っているので、「action」に「sourceの1つひとつの要素」を渡せば良いってことになるんだ。「action(item)」は「itemのactionに登録されているメソッドを呼び出してね」って意味になるんだよ。

使用方法

定義ができたので使ってみるよ…ありがちなレコードの一括削除をやってみるよ。

今まで

private void Button_Click(object sender, RoutedEventArgs e)
{
    TestDataSet l_TestDataSet = new TestDataSet();
    TestDataSet.TestDataTableRow[] l_TestDataTableRows =
        l_TestDataSet
        .TestDataTable
        .Where(x =>
            x.DataColumn1 == "test")
        .ToArray();
    foreach(TestDataSet.TestDataTableRow f_TestDataTableRow in l_TestDataTableRows)
    {
        f_TestDataTableRow.Delete();
    }
}

拡張メソッドを利用

private void Button_Click(object sender, RoutedEventArgs e)
{
    TestDataSet l_TestDataSet = new TestDataSet();
    TestDataSet.TestDataTableRow[] l_TestDataTableRows =
        l_TestDataSet
        .TestDataTable
        .Where(x =>
            x.DataColumn1 == "test")
        .ToArray();
    l_TestDataTableRows.ForEach(x => x.Delete());
}

テーブルアダプターでFillをしていないとんでもないソースだけど…今までは「foreach」ステートメントを利用しないといけなかったのが、一行ですむようになってるんだ。

標準偏差を求める拡張メソッドを追加してみる

今度は配列の値を利用したメソッドを追加してみるよ。前出の説明にあったように「Func」を使うよ。

public static class IEnumerableEx
{
    public static Double StdDev<T>(this IEnumerable<T> source, Func<T, Double> Value)
    {
        Double l_Average = source.Average(item => Value(item));
        Double l_Result = 0;
        foreach (T item in source)
        {
            l_Result +=
                (Value(item) - l_Average) *
                (Value(item) - l_Average);
        }
        return Math.Sqrt(l_Result / source.Count());
    }
}

使うときはこんな感じだよ。

private void Button_Click(object sender, RoutedEventArgs e)
{
    TestDataSet l_TestDataSet = new TestDataSet();
    TestDataSet.TestDataTableRow[] l_TestDataTableRows =
        l_TestDataSet
        .TestDataTable
        .Where(x =>
            x.DataColumn1 == "test")
        .ToArray();
    Double l_StdDev = l_TestDataTableRows.StdDev(x => x.DataColumn4);
}

こんな感じになるんだ。「標準偏差を計算してみる」に掲載した処理を型ごとにコーディングするより、こっちのほうがずっと便利だよね。

参考サイト

ジェネリック - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
デリゲート - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
【LINQの前に】ラムダ式?デリゲート?Func<T, TResult>?な人へのまとめ【知ってほしい】 - Qiita
LINQにオレオレ機能を追加 - xin9le.net