Effective C# 3rd 読書メモ 32 イテレーションとAction/Predicate/Functionの間の結合度を低くする

イテレーターメソッドでは、主に2種類の仕事をする。1つは一連の要素の繰り返しで、もう1つはそれぞれの要素に基づく処理(集計等)である。これらは別々のイテレーターメソッドで扱うべきである。

これらの仕事を行うには、デリゲートを使うのがよい。デリゲートの中でも、特に汎用的なものはAction<T>Func<T, TResult>Predicate<T>として定義されている。

Predicate<T>はbooleanを返す。フィルタリング処理等に使用する。以下はfilterFuncで絞り込みを行うWhereメソッドの実装例。

Func<T, TResult>の実装例は以下。

Effective C# 3rd 読書メモ 31 合成可能なAPIを作る

イテレーターメソッドの結果を次のイテレーターメソッドにつなげるような呼び出しを行うと、イテレーターは全体で1回だけ実行される(遅延評価される)。

上記コードを実行すると、その出力は以下のようになる。「{1, 2, 2, 3}」という配列を前から順に1回だけ走査していることがわかる。

このように、イテレーターメソッドを使うと、通常のループに比べて、関数を合成するような使い方ができる。

Effective C# 3rd 読書メモ 30 ループよりもクエリ構文を使う

ループは手続き的な構文だが、LINQのクエリは宣言的な構文である。できることは同じでも、クエリ構文を使ったほうが、よりコードの意味が伝わりやすくなる。

以下のコードはループを使った例である。

同じ処理を、LINQを使うと以下のように書くことができる。

一般に、コレクションを操作するロジック(絞り込み、並び替え等)が複雑になる場合は、ループよりもLINQの方がわかりやすいコードを書くことができる。

Effective C# 3rd 読書メモ 29 コレクションを返すよりもイテレーターメソッドを使う

一連の要素をメソッドから返す際は、イテレーターメソッドを作成すべきである。そうすることで、呼び出し側の使い勝手が良くなるし、コレクション全体を取得して返すよりもパフォーマンスが良くなる。

イテレーターメソッドとは、yield returnを使って一連の要素を返す方法である。

イテレーターメソッドは、最初の要素が要求されるまで実行されない。このことは、エラーの発生箇所がわかりづらいという問題を生み出す。この問題の解決のためには、以下のように、ジェネレータ関数を別メソッドとして用意するというイディオムを利用できる。

上記サンプルのGenerateAlphabetSubset()メソッドに対して誤った呼び出しを行うと、呼び出しの時点でバリデーションが実行されるため、エラーに気づきやすい。

Effective C# 3rd 読書メモ 28 特定の型のコレクションに拡張メソッドを用意することを検討する

List<int>のような、組み込みの型を使用したコレクションで、さらに追加の機能が必要な場合、拡張メソッドを使って実装できる。

たとえば、System.Linq.Enumerableには、IEnumerable<T>だけでなく、型に特化した拡張メソッドも定義されている。以下はIEnumerable<int>に特化したAverage()メソッドである。

Cutomerというクラスのオブジェクトのコレクションに対して専用のクエリを発行するメソッドがほしい、といった場合には、IEnumerable<Customer>を拡張したCustomerListのようなクラスを作成するよりも先に、拡張メソッドの作成を検討すべきである。
一般に、派生クラスよりも拡張メソッドのほうが再利用性が高い。