WonderPlanet DEVELOPER BLOG

ワンダープラネットの開発者ブログです。モバイルゲーム開発情報を発信。

C# LINQ小技

今回のエンジニアブログを担当する加賀です。

C# 3.5でのLINQの小技を紹介したいと思います。
今回のコードはVisual Studio 2010 Pro SP1、C# 3.5で確認しています。

複数の配列から重複を除いた一覧を取得

class PartyData {  
    public int[] unitIds;    // ユニット番号  
}  
List<PartyData> partyList;  

以上のようなクラスとメンバーで、partyList内のunitIds配列から、ユニット番号を重複なしで取得します。

LINQを使わない場合は以下のような処理が考えられると思います。

// partyListの初期化  
List<PartyData> partyList = new List<PartyData> () {  
    new PartyData(){ unitIds = new[]{3,  5,  7, 10} },  
    new PartyData(){ unitIds = new[]{5, 12, 13, 15} },  
    new PartyData(){ unitIds = new[]{7, 13, 15, 20} }  
};  
  
// 処理  
List<int> ids = new List<int> ();  
foreach (PartyData data in partyList) {  
    foreach (int value in data.unitIds) {  
        if (!ids.Contains (value)) {  
            // まだidsリストにない値のみ追加  
            ids.Add (value);  
        }  
    }  
}  

LINQのSelectManyDistinctを使用すれば、より簡潔に記述できます。

SelectManyは、シーケンスの要素に対してシーケンスを返すデリゲートを使用し、最後にそれぞれ返されてきたシーケンスを一つにまとめてくれます。
Distinctは、シーケンスから重複する要素を除いたシーケンスを返してくれます。

// 処理  
List<int> ids = partyList  
    .SelectMany (data => data.unitIds)  // int[](シーケンス)を返す  
    .Distinct ()                        // 重複排除  
    .ToList ();  

シーケンスから直接ForEachを呼び出す

Listなら直接呼び出せるList.ForEach(Action action)があります。
ただ、配列には直接呼び出す形のものは存在せず、System.Arrayクラスのstatic関数、Array.ForEach(T[] array, Action action)として存在します。
さらに、LINQ途中のシーケンスであるIEnumerableには、戻り値がvoid型の全要素処理メソッドは存在しません。

これらを実現するには拡張メソッドとして実装します。
拡張メソッドとは、以下の条件で実装すると、その型のインスタンスメソッドのように呼び出す事が出来ます。

  • staticクラス
  • public staticメソッド
  • 第1引数にて、型の前にthisをつけて、呼び出させたい型を指定

ただし、呼び出させたい型のpublicなメソッド、プロパティ、フィールドのみアクセス可能です。

using System;  
public static class Utils {  
    // 配列版  
    // int[] intArray;  
    // intArray.ForEach (~); のように呼び出せる  
    // 本来は Array.ForEach (intArray, ~);  
    public static void ForEach<T> (this T[] source, Action<T> action) {  
        Array.ForEach (source, action);  
    }  
  
    // IEnumerable<T>版  
    // Enumerable.Repeat (0, 5).ForEach (~); のように呼び出せる  
    // 本来は Enumerable.Repeat (0, 5).ToList ().ForEach (~); か  
    //     Array.ForEach (Enumerable.Repeat (0, 5).ToArray (), ~);  
    public static void ForEach<T> (this IEnumerable<T> source, Action<T> action) {  
        foreach (T val in source) {  
            action (val);  
        }  
    }  
}  
まとめ

LINQをうまく活用すると、ループさせる部分を記述しなくても、シーケンスの要素それぞれの処理を行えます。
ForEachは、配列やIEnumerableでは記述量が増えてしまいますが、拡張メソッドを記述しておくだけでListと同じように使用できます。
LINQをうまく活用して、ループ処理や汎用処理をわざわざ書かなくてもいいようにしていきたいです。