C# 2.0だと……
C# 2.0になるとよりよいコードが書けるようになります。まずは型安全性から片づけましょう。
static Employee[] GoldWatch(IEnumerable<Employee> employees) { // 可変長配列を用意し、 List<Employee> results = new List<Employee>(); // 条件を満たす要素を追加する foreach ( Employee employee in employees ) { if ( employee.Years > 3 ) { results.Add(employee); } } // 配列を取り出す return results.ToArray(); }
C# 2.0で導入されたGenericsにより、シンプルな実装を維持したまま型の安全性を確保できます。上記のコードでは、要素の型がEmployee
でない配列やコレクションを与えると、コンパイル時にエラーとしてくれます。
要素全てを調べ上げてしまう無駄
さて、まだ問題が残っています。ここまでに挙げたコードはいずれも条件を満たす全ての要素を内包した配列を返しています。が、もし呼び出し側が最初の数個しか必要としないとしたら、条件を満たすものを見つけるべく集合内の要素を全て調べ上げているのはまったくの無駄となります。
この問題は、C# 2.0で追加された新機能「yield」による列挙を用いて解決します。配列ではなくIEnumerable<Employee>
を戻り値とすることで、今までの無駄な列挙をなくすことができます。
static IEnumerable<Employee> GoldWatch(IEnumerable<Employee> employees) { foreach ( Employee employee in employees ) { // 条件を満たす要素が見つかるたびに if ( employee.Years > 3 ) { // yieldによってそれを返す yield return employee; } } }
これでEmployee
の一時的なコピー領域を排除できました。例えば、呼び出し側で最初の要素だけを必要としていたとき、GoldWatch
内では全要素を列挙することがないのでより高速に動作します。
違う項目を検索する場合
さて、このあとまた同様の検索/列挙が必要となったらどうしましょう? 例えば「営業」に所属する従業員の一覧が欲しくなったとしましょう。おそらくGoldWatch
とほとんど同じ、唯一検索条件だけが異なるメソッドSalesForce()
を新たに実装することになるのではないでしょうか。そうやって検索条件だけが異なるメソッドを漫然と書き並べるのは効率的とは言い難いものがあります。
delegateを使って汎用的なFilter
メソッドを実装しましょう。
delegate bool Choose(Employee employee); static IEnumerable<Employee> Filter( IEnumerable<Employee> employees, Choose choose) { // employeesの各要素:employeeに対し、 foreach ( Employee employee in employees ) { // choose(employee) が真となるものを返す if ( choose(employee) ) { yield return employee; } } } // 金時計をもらえる条件 static bool GoldWatchChoose(Employee employee) { return employee.Years > 3; } // 営業に所属する条件 static bool SalesForceChoose(Employee employee) { return employee.Department == "Sales"; } static IEnumerable<Employee> GoldWatch(IEnumerable<Employee> s) { return Filter(employees, new Choose(GoldWatchChoose)); } static IEnumerable<Employee> SalesForce(IEnumerable<Employee> s) { return Filter(employees, new Choose(SalesForceChoose)); }
このケースでは新たな検索条件が必要となるたびに問い合わせメソッド(GoldWatch
、SalesForce
)およびそれらに対応した条件判断メソッド(GoldWatchChoose
、SalesForceChoose
)の組を追加することになり、保守性の面で芳しくありません。
そこでC# 2.0の機能、匿名delegateを使ってみましょう。
static IEnumerable<Employee> GoldWatch(IEnumerable<Employee> s) { return Filter(employees, delegate(Employee employee) { return employee.Years > 3; } ); } static IEnumerable<Employee> SalesForce(IEnumerable<Employee> s) { return Filter(employees, delegate(Employee employee) { return employee.Department == "Sales"; } ); }
匿名delegateによって検索条件を直接書き下すことができるため、GoldWatchChoose
、SalesForceChoose
が不要となりました。