2011年06月29日

.NETのEnumerable拡張メソッド

まえがき


今回は.NETの、コレクションの拡張メソッドについてです。
なお、サンプルコードは.NET Framework4のC#です。
.NET Framework3.5でもだいたい動くと思います。


例えば、配列の要素の和を求めるにはどうしたら良いでしょうか?

和を求める
var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

//普通のやり方
var sum = 0;
for (int i = 0; i < numbers.Length; i++)
{
sum += numbers[i];
}

//コレクションの拡張メソッドを使うやり方
var sum = numbers.Sum();





背景


コレクションの拡張メソッドは.NET Framework3.5で実装されました。
おそらくLINQで使うためなのでしょうが、上記の例のようにLINQとは関係なく使えます。
わりと便利なのですが、あまり流行ってないというか、知られていないような気がします。
思うに、なんとなく難しそうだからでしょうか?
でもこいつを使うとすっきり1行で書けて嬉しくなれます。
この記事ではとりあえず気軽に使えそうなものを挙げてみました。
どんなことができるか、下の方の目次を見てください。




注意事項


・System.Linq名前空間をインポート
しないと使えません。
クラスの頭に「using System.Linq;」と書いておきます。

・「コレクション」とは?
ここで言う「コレクション」とはIEnumerableインターフェイスを実装するクラスのことです。
平たく言うと、普通の配列、List、Dictionaryなど、よく使う配列っぽいもの。

・用法・用量を守って
拡張メソッドで1行で書いても、内部的にはコレクションに対するループが回っています。
連発すると重くなるかもしれません。
逆に言うと、些細な処理なら気軽に使ってOKってこと。

・どこにあるの?
コレクションの拡張メソッドの実体はEnumerableクラスに実装されています。
MSDNの該当ページはこちら

・網羅されてる?
この記事では「単品で簡単に使えそうなもの」って感じで挙げてます。
例えばGroupBy関数などは単品で使えなさそうなので挙げてません。
ついでに、各関数にMSDNに対するリンクを張っています。
詳しくはMSDNをみてください。

・型とかよくわからん
MSDNを見てもよくわからない型がたくさん出てきます。
大丈夫、あまり気にしなくてOK。
戻り値はvarで受けると幸せ。




目次


和(Sum)
要素の個数(Count)
最大・最小(Min)
平均(Average)
好きなように集計(Aggregate)

コレクションを連結(Concat)
重複要素を排除(Distinct)
ソート(OrderBy、ThenBy)
順序を反転(Reverse)
マージ(Zip)

条件を満たす要素をコレクションで取得(Where)
指定したインデックスの要素(ElementAt)
コレクションの一部を切り出す(Take、Skip)
条件を満たす最初・最後の要素(First、Last)
条件を満たす唯一の要素(Single)
空の場合にデフォルト値を返す(DefaultIfEmpty)

全要素が条件を満たすか(All)
条件を満たす要素が存在するか(Any)
指定した要素が存在するか(Contains)

和集合(Union)
積集合(Intersect)
差集合(Except)

連番を生成(Range)
同じ値を繰り返すコレクションを生成(Repeat)





Sum

各要素の和を求めます。
引数に変換関数を与えられます。

var people = new[] {
new { Name = "一郎", Height = 1.65, Weight = 50 },
new { Name = "二郎", Height = 1.75, Weight = 60 },
new { Name = "三郎", Height = 1.55, Weight = 60 }
};

//全員の体重の合計を求める。
var weightTotal = people.Sum(person => person.Weight);
//weightTotalは170





要素の個数


Count

要素の個数を数えます。
引数にカウントする条件を与えられます。

var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

//要素の個数を数える。
var count = numbers.Count();
//countは10

//偶数の個数を数える。
count = numbers.Count(n => n % 2 == 0);
//countは5





最大・最小


Max
Min

最大・最小を求めます。
引数に変換関数を与えられます。

var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

//最大を求める
var max = numbers.Max();
//maxは10

//最小を求める
var min = numbers.Min();
//minは1

var people = new[] {
new { Name = "一郎", Height = 1.65, Weight = 50 },
new { Name = "二郎", Height = 1.75, Weight = 60 },
new { Name = "三郎", Height = 1.55, Weight = 60 }
};

//最大のBMIを求める。
var maxBmi = people.Max(p => p.Weight / p.Height / p.Height);
//maxBmiは24.973985431841832





平均


Average

平均を求めます。
引数に変換関数を与えられます。

var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

//平均を求める。
var average = numbers.Average();
//averageは5.5

//各要素を2乗したものの平均を求める。
average = numbers.Average(n => n * n);
//averageは38.5





好きなように集計


Aggregate

好きなように集計します。
引数に、集計前の初期値、集計処理、集計後の処理、を与えられます。

var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

//相乗平均を求める。
var geometricMean = numbers.Aggregate(
1,//初期値
(product, next) => product * next,//集計処理
result => Math.Pow(result, 1.0 / numbers.Length)//集計後の処理
);
//geometricMeanは4.528・・・。

//4の階乗を求める。
var factorial = Enumerable.Range(1, 4).Aggregate((product, next) => product * next);
//factorialは24。





コレクションを連結


Concat

コレクションを連結します。
連結対象の型が異なっても大丈夫です。
例では配列とListを連結しています。

var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

//連結する。
//型が異なっても大丈夫。
var concat = numbers.Concat(new List<int>() { 0, 0, 0, 0, 0 });
//concatは{ 1, 2,・・・, 10, 0, 0, 0, 0, 0 }





重複要素を排除


Distinct

重複した要素を排除します。

var letters = new[] { "A", "a", "B", "c", "a", "B", "c", "d" };

//重複を排除する。
var distinct = letters.Distinct();
//distinctは{ "A", "a", "B", "c", "d" }





ソート


OrderBy
OrderByDecending
ThenBy
ThenByDecending

ソートします。
第1ソートキーはOrderByで、第2ソートキー以降はThenByで指定します。
Decendingが付く関数は降順で、付かない関数は昇順でソートします。

var people = new[] {
new { Name = "一郎", Height = 1.65, Weight = 50 },
new { Name = "二郎", Height = 1.75, Weight = 60 },
new { Name = "三郎", Height = 1.55, Weight = 60 }
};

//体重をキーに昇順にソートする。
var orderBy = people.OrderBy(person => person.Weight);
//orderByは一郎、二郎、三郎、の順に並ぶ。

//体重、身長をキーに昇順にソートする。
orderBy = people.OrderBy(person => person.Weight).ThenBy(person => person.Height);
//orderByは一郎、三郎、二郎、の順に並ぶ。





順序を反転


Reverse

順序を反転します。

var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

//順序を反転する。
var reverse = numbers.Reverse();
//reverseは{ 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 }





マージ


Zip

二つのコレクションをマージして一つのコレクションにします。
マージ後の型はラムダ式の戻り値により自動で決まります。
例ではstringで返しているのでstringのコレクションが返ります。
マージ対象の型が異なっても大丈夫です。
例では配列とListをマージしています。

var names = new[] { "一郎", "二郎", "三郎" };
var ages = new List<int>() { 20, 17, 15 };

//マージする。
//型が異なっても大丈夫。
var zip = names.Zip(ages, (name, age) => string.Format("{0}は{1}歳", name, age));
//zipは{"一郎は20歳", "二郎は17歳", "三郎は15歳"}





条件を満たす要素をコレクションで取得


Where

条件を満たす要素をコレクションで取得します。
Whereで件数を絞って、更に処理を行うのが一般的な使い方でしょうか。

var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

//偶数を取得する。
var even = numbers.Where(n => n % 2 == 0);
//evenは{ 2, 4, 6, 8, 10 }





指定したインデックスの要素


ElementAt
ElementAtOrDefault

指定したインデックスの要素を取得します。
ElementAtOrDefaultでデフォルト値を指定しておけば、ArgumentOutOfRangeExceptionが発生しません。

var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

//0番目の要素を取得する。
var element = numbers.ElementAt(0);
//elementは1

//インデックスが10の要素はないので、デフォルト値(この場合はintのデフォルト)が返る。
element = numbers.ElementAtOrDefault(10);
//elementは0





コレクションの一部を切り出す


Take
TakeWhile
Skip
SkipWhile

コレクションの一部を切り出します。
文字列で言うところのSubstringみたいなものです。
TakeWhileとSkipWhileは、細かい条件を設定できます。
あまり使い道がなさそうな気もしますが。

var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

//最初の3個を取得する。
var cut = numbers.Take(3);
//cutは{ 1, 2, 3 }

//要素を最初から見ていって、条件が満たされてる間は取得する。
//満たされなくなったら残りを捨てる。
//上記の例と同じ動き。
cut = numbers.TakeWhile((n, index) => index < 3);
//cutは{ 1, 2, 3 }

//最初の3個を捨てて、残りを取得する。
cut = numbers.Skip(3);
//cutは{ 4, 5, 6, 7, 8, 9, 10 }

//要素を最初から見ていって、条件が満たされてる間は捨てる。
//満たされなくなったら残りを取得する。
//上記の例と同じ動き。
cut = numbers.SkipWhile((n, index) => index < 3);
//cutは{ 4, 5, 6, 7, 8, 9, 10 }

//3個目から8個目を切り出す。
cut = numbers.Take(8).Skip(2);
//cutは{ 3, 4, 5, 6, 7, 8 }





条件を満たす最初・最後の要素


First
FirstOrDefault
Last
LastOrDefault

条件を満たす最初・最後の要素を取得します。
引数が空の場合は、条件なしでまさに最初・最後の要素を取得します。

var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

//最初の偶数を求める。
var first = numbers.First(n => n % 2 == 0);
//firstは2

//負数の要素はないので、デフォルト値(この場合はintのデフォルト)が返る。
first = numbers.FirstOrDefault(n => n < 0);
//firstは0

//最後の偶数を求める。
var last = numbers.Last(n => n % 2 == 0);
//lastは10

//負数の要素はないので、デフォルト値(この場合はintのデフォルト)が返る。
last = numbers.LastOrDefault(n => n < 0);
//lastは0





条件を満たす唯一の要素


Single
SingleOrDefault

条件を満たす唯一の要素を取得します。
唯一ではない、存在しない場合は、デフォルト値を返したり例外を発生させたりできます。

var people = new[] {
new { Name = "一郎", Height = 1.65, Weight = 50 },
new { Name = "二郎", Height = 1.75, Weight = 60 },
new { Name = "三郎", Height = 1.55, Weight = 60 }
};

//「一郎」という名前の人を取得する。
var single = people.Single(person => person.Name == "一郎");

//体重が60の人を取得したいが、複数いる。
single = people.Single(person => person.Weight == 60);
//InvalidOperationException発生。

//体重が80の人を取得したいが、いない。
single = people.SingleOrDefault(person => person.Weight == 80);
//デフォルト値(この場合はnull)が返る。





空の場合にデフォルト値を返す


DefaultIfEmpty

コレクションが空の場合にデフォルト値を返します。
空でない場合はそのまま返します。
コレクションの要素数が0だと困るときに有用です。

var empty = new int[] { };

foreach (var n in empty.DefaultIfEmpty(0))
{
//nは0
//foreachは1回しか回らない。
}





全要素が条件を満たすか


All

全要素が条件を満たすか調べます。

var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

//全て偶数かどうか調べる。
var isEven = numbers.All(n => n % 2 == 0);
//isEvenはfalse





条件を満たす要素が存在するか


Any

条件を満たす要素が存在するか調べます。
引数に条件判定式を与えるのがContainsとの違いです。

var empty = new int[] { };

//要素が存在するか調べる(isExistはfalse)。
var isExist = empty.Any();

var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

//偶数が存在するか調べる。
isExist = numbers.Any(n => n % 2 == 0);
isExistはtrue





指定した要素が存在するか


Contains

指定した要素が存在するか調べます。
引数に要素そのものを与えるのがAnyとの違いです。

var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

//「10」が含まれているか調べる。
var isContain = numbers.Contains(10);
//isContainはtrue





和集合


Union

和集合を求めます。
対象の型が異なっても大丈夫です。
例では配列とListの和集合を求めています。

var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

//和集合を求める。
//型が異なっても大丈夫。
var union = numbers.Union(new List<int>() { 6, 7, 8, 9, 10, 11, 12 });
//unionは{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }





積集合


Intersect

積集合を求めます。
対象の型が異なっても大丈夫です。
例では配列とListの積集合を求めています。

var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

//積集合を求める。
//型が異なっても大丈夫。
var intersect = numbers.Intersect(new List<int>() { 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 });
//intersectは{ 2, 4, 6, 8, 10 }





差集合


Except

差集合を求めます。
対象の型が異なっても大丈夫です。
例では配列とListの差集合を求めています。

var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

//差集合を求める。
//型が異なっても大丈夫。
var excepted = numbers.Except(new List<int>() { 1, 2, 4, 8 });
exceptedは{ 3, 5, 6, 7, 9, 10 }





連番を生成


Range

連番を生成します。
第1引数は初期値、第2引数は生成する個数です。

//1〜100の整数の連番を生成する。
var sequence = Enumerable.Range(1, 100);
//sequenceは{ 1, 2, ・・・ , 100 }





同じ値を繰り返すコレクションを生成


Repeat

同じ値を繰り返すコレクションを生成します。
使い道があるのでしょうか?

//「A」が5個格納されたコレクションを生成する
var repeat = Enumerable.Repeat("A", 5);
//repeatは{ "A", "A", "A", "A", "A" }




ラベル:.net
posted by ぺるたご at 15:05| Comment(0) | TrackBack(0) | 日記 | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。

この記事へのトラックバック
×

この広告は1年以上新しい記事の投稿がないブログに表示されております。