拡張メソッドのターゲットにIList(T)とIEnumerable(T)のどちらを選ぶか?
.NET Frameworkのクラスライブラリでは、IEnumerable(T)に対して多数の拡張メソッドが用意されています。これは非常に便利です。ここで自作の拡張メソッドを作成する場合に悩むことがあります。集合に対する拡張をしたい場合、IEnumerable(T)向けに拡張メソッドを書くと柔軟性が高まることは確かですが、本質的にIEnumerable(T)のメソッドであるべきだろうか?と悩むことがあります。具体例を挙げたいと思います。私は順列を生成する以下のようなメソッドを定義しました。
/// <summary> /// 順列を生成する /// </summary> /// <param name="elements">順列の要素</param> /// <param name="selectionCount">要素から何個選択するか</param> /// <param name="permitRepeatedUse">要素から取る際に同じ要素を重複して取ることを許可するか</param> /// <returns>順列のIEnumerable(T)</returns> public static IEnumerable<T[]> CreatePermutation<T>(this IList<T> elements, int selectionCount, bool permitRepeatedUse)
第一引数は順列を生成するための要素のデータで、例えば、new string[]{"a","b","c"}などです。
順列を生成する対象データは通常、要素数が最終的には確定している配列かリストです。(IList(T)を実装していないコレクションもありますが、ここではとりあえず気にしません) 実用上はIList(T)を対象にすれば問題ないだろうと考えました。もしIEnumerable(T)を対象にしたければ、IEnumrable(T)の拡張メソッドToArray、ToListを使えばよいだけです。
逆にIEnumerable(T)を対象とする拡張メソッドとしなかった理由は以下です。
- 順列を生成する対象の集合は、生成する時点では要素数が固定であるはず
- 順列を生成するには要素数とインデックスによるアクセスが必要
- IEnumerable(T)では要素数を取るのはCount、インデックスによるアクセスはElementAtといった拡張メソッドに頼らざるを得ないが、これらのメソッドはたぶん遅いはず。
- なぜなら、IEnumerable(T)は順番に要素を挙げていくことしかできないから。
- といいつつ、ひょっとしたらCountやElementAtメソッドは内部でインスタンスの型を見てIList(T)だったらこうみたいな場合わけをしているかもしれないけれど。
つまり、IEnumerable(T)を対象とすることで得られる柔軟性よりも、IList(T)を対象とすることであるべき姿に近いのではないか、処理効率がよいのではないか、という考えが勝りました。でも、他の拡張メソッドと使い方が異なるという面で一貫性がないなぁなどと思ったりもするのですよね。(異なったものはあえて異なって見せるべきという意見もあるけれど、今回の場合、"異なった"と言えるかどうかが自信なし) みなさんはどう思いますか?
以下は順列生成メソッドのソースです。
using System; using System.Collections.Generic; namespace YKLib.Extentions { public static class IListExtentions { /// <summary> /// 順列を生成する /// </summary> /// <param name="elements">順列の要素</param> /// <param name="selectionCount">要素から何個選択するか</param> /// <param name="permitRepeatedUse">要素から取る際に同じ要素を重複して取ることを許可するか</param> /// <returns>順列のIEnumerable(T)</returns> public static IEnumerable<T[]> CreatePermutation<T>(this IList<T> elements, int selectionCount, bool permitRepeatedUse) { if (elements == null) throw new ArgumentNullException("elements"); if (selectionCount < 0) throw new ArgumentOutOfRangeException("selectionCount"); if (!permitRepeatedUse && elements.Count < selectionCount) throw new ArgumentException("重複許可のない順列では要素数以上のselectionCountは指定できません。"); return permitRepeatedUse ? CreateRepeatedPermutation<T>(elements, selectionCount) : CreatePermutation<T>(elements, selectionCount); } /// <summary> /// 重複を許可しない順列を生成する /// </summary> /// <param name="elements">順列の要素</param> /// <param name="selectionCount">要素から何個選択するか</param> /// <returns>順列のIEnumerable(T)</returns> public static IEnumerable<T[]> CreatePermutation<T>(this IList<T> elements, int selectionCount) { if (elements == null) throw new ArgumentNullException("elements"); if (selectionCount < 0) throw new ArgumentOutOfRangeException("selectionCount"); if (elements.Count < selectionCount) throw new ArgumentException("要素数以上のselectionCountは指定できません。"); int elementCount = elements.Count; int totalCountOfPermutation = (int)Math.Pow(elementCount, selectionCount); // 要素数elementCountの0〜selectionCount-1までのべき乗をあらかじめ求めておく // 後の計算の高速化のため int[] powers = new int[selectionCount]; for (int i = 0; i < selectionCount; i++) { powers[i] = (int)Math.Pow(elementCount, i); } HashSet<int> hashSet = new HashSet<int>(); T[] permutation = new T[selectionCount]; for (int i = 0; i < totalCountOfPermutation; i++) { hashSet.Clear(); for (int j = 0; j < selectionCount; j++) { int index = (i / powers[j]) % elementCount; if (!hashSet.Add(index)) { break; } permutation[selectionCount - 1 - j] = elements[index]; } if (hashSet.Count == selectionCount) { yield return permutation; } } } /// <summary> /// 重複を許可する順列を生成する /// </summary> /// <param name="elements">順列の要素</param> /// <param name="selectionCount">要素から何個選択するか</param> /// <returns>順列のIEnumerable(T)</returns> public static IEnumerable<T[]> CreateRepeatedPermutation<T>(this IList<T> elements, int selectionCount) { if (elements == null) throw new ArgumentNullException("elements"); if (selectionCount < 0) throw new ArgumentOutOfRangeException("selectionCount"); int elementCount = elements.Count; int totalCountOfPermutation = (int)Math.Pow(elementCount, selectionCount); // 要素数elementCountの0〜selectionCount-1までのべき乗をあらかじめ求めておく // 後の計算の高速化のため int[] powers = new int[selectionCount]; for (int i = 0; i < selectionCount; i++) { powers[i] = (int)Math.Pow(elementCount, i); } T[] permutation = new T[selectionCount]; for (int i = 0; i < totalCountOfPermutation; i++) { for (int j = 0; j < selectionCount; j++) { int index = (i / powers[j]) % elementCount; permutation[selectionCount - 1 - j] = elements[index]; } yield return permutation; } } } }