特集

C# 2.0新機能徹底解説(前編)

開発生産性を飛躍的に高めるジェネリック

菊池 和彦
Microsoft MVP Visual Developer - Visual C#)
2004/11/23
Page1 Page2 Page3 Page4

■データの検索:FindIndexメソッドおよびPredicate<T>デリゲート

 ある一定の条件に合致するデータを検索するには、FindIndexメソッドを使うことができる。FindIndexメソッドはPredicate<T>デリゲートを受け取り、Predicate<T>デリゲートがTrueを返した項目について、そのインデックスを戻り値で示してくれる。Predicate<T>デリゲートは次のように宣言されている。

public sealed delegate bool Predicate<T>(T obj);

 ある名前の商品が含まれているItemsコレクション(=List<T>コレクション)の最初の行は、以下のコードで取得できる。

class FindOrderLineByNamePredicate
{
  public string name;
  // Predicate<T>デリゲートの処理を実装
  public bool Match( OrderLine ol )
  {
    return ol.prod.Name==name;
  }
}

public int FindOrderLineIndexByName( string name )
{
  FindOrderLineByNamePredicate
      pred = new FindOrderLineByNamePredicate();
  pred.name = name;
  // FindIndexメソッドにより条件に合致するデータを検索
  return Items.FindIndex( pred.Match );
}
FindIndexメソッドとPredicate<T>デリゲートを利用してデータ検索を行うサンプル・コード

 次回で詳しく説明するが、ジェネリックと同じくC# 2.0の新機能である「匿名メソッド」を使うと、このコードはさらに劇的に短くなり、具体的には以下のようなコードになる。

public int FindOrderLineIndexByName( string name )
{
  return Items.FindIndex(
    delegate (OrderLine ol)
      { return ol.prod.Name==name; }
);
}
匿名メソッドによりさらに短くなった、FindIndexメソッドとPredicate<T>デリゲートを利用したサンプル・コード
「pred.Match」というデリゲートが、デリゲート・メソッドの「実体そのもの」(=匿名メソッド)に置き換えられている。これにより、先ほどのサンプル・コードにあった「public bool Match( OrderLine ol )」というデリゲート・メソッドは不要となる。

■データの抽出:FindAllメソッドおよびPredicate<T>デリゲート

 Predicate<T>デリゲートは検索だけでなく抽出にも使われる。抽出は指定された条件を満たす項目だけの(つまりPredicate<T>デリゲートがTrueを返したデータだけを含む)Itemsコレクションを返す。

 例えば、注文個数が10個以上のOrderLine構造体データ(明細行)を抽出するには、以下のようなコードを記述すればよい。

public List<OrderLine> FindOrderLineGraterThan10Amounts()
{
  return Items.FindAll(
    delegate (OrderLine ol) { return ol.amount>=10; } );
}
FindIndexメソッドとPredicate<T>デリゲートを利用してデータ抽出を行うサンプル・コード(匿名メソッドを使用)

 また、検索や抽出だけでなく、指定された条件を満たすものをリストから削除したい場合にはRemoveAllメソッドが使用できる。この場合もPredicate<T>デリゲートが利用される。同様に、条件を満たすものがあるかの判定にはExistsメソッドが、すべてが条件を満たすかの判定にはTrueForAllメソッドが使用でき、それぞれPredicate<T>デリゲートが利用される。

【コラム】Predicate<T>デリゲートをより便利にするショート・コード
 Predicate<T>デリゲートを使って、And(論理積)、Not(否定)、Or(論理和)、Xor(排他的論理和)などの論理演算を使用した条件指定を行うこともできる。詳しくは以下のサンプル・プログラムを参照してほしい。

// Predicate<T>デリゲートをより便利にするショート・コード
class And<T>
{
  Predicate<T> p1;
  Predicate<T> p2;

  public And( Predicate<T> p1,Predicate<T> p2 ) {
    this.p1 = p1; this.p2=p2;
  }
  // Predicate<T>デリゲートの実装
  bool Pred( T t ) { return p1(t) && p2(t); }

  public static implicit operator Predicate<T>( And<T> from ) {
     return from.Pred;
  }
}
Predicate<T>デリゲートをより便利にするショート・コード

 このようなコードにより、複数のPredicate<T>デリゲートをAnd接続してデータ抽出を行うことができる。

Items.FindAll(
  new And<OrderLine>(
    delegate (OrderLine ol) { return ol.amount > 10; },
    delegate (OrderLine ol) { return ol.prod.Price > 1000; }) );

 同様にして、NotやOr、Xorも簡単に作れるだろう。

■データ変換:ConvertAll<U>メソッドとConverter<T, U>デリゲート

 データの形式を変換するには、ConvertAll<U>メソッドとConverter<T, U>デリゲートを使用する。Converter<T, U>デリゲートは次のように宣言されている。

public sealed delegate U Converter<T, U>(T from);

 注文された商品を発送する段階で、ItemsコレクションのOrderLine構造体データをDeliveryLine構造体データに変換する必要があるとしよう。この場合、OrderLine構造体データをパラメータに取りDeliveryLine構造体データを戻り値として返すメソッドが必要となるが、それがConverter<T, U>デリゲートの実装となる。

// Converter<T, U>デリゲートの処理を実装
static DeliveryLine ToDeliveryLine( OrderLine ol )
{
  return new DeliveryLine( ol );
}

public DeliveryOrder MakeDelivery()
{
  DeliveryOrder deliv = new DeliveryOrder();
  // ConvertAll<U>メソッドによりすべてのデータを変換
  deliv.Items.AddRange(
      Items.ConvertAll <DeliveryLine> ( ToDeliveryLine ) );
}
ConvertAll<U>メソッドとConverter<T, U>デリゲートを利用してデータ変換を行うサンプル・コード

 「ConvertAll」の後ろに変換先の型(この例では「<DeliveryLine>」)を指定しなければならないことに注意しよう。

■ジェネリックを使ったコレクション・クラスの拡張について

 ところで、上記のコードはそれなりに単純だが、筆者は.NET FrameworkのConvertAll<U>メソッドには若干の不満を持っている。その理由は単純で、Predicate<T>デリゲートが指定できないからである。

 特定の条件を持っているものだけを変換したい場合にはFindAllメソッドで抽出を行い、その結果を変換することになる。そのため、以下のコードのようにコーディングすることが可能だが、コード中ではList<T>コレクションを2つも消費する。

public DeliveryOrder MakeDelivery()
{
  DeliveryOrder deliv = new DelivOrder();
  deliv.Items.AddRange(Items.FindAll( IsReadyDeliver ).ConvertAll <DeliveryLine> ( ToDeliveryLine ) );
}

 FindAllメソッドがList<T>コレクションを返し、ConvertAll<U>メソッドがまたList<T>コレクションを返すので、メモリを無駄に消費してしまう。この場合のOrderLine構造体データは値型なので、List<T>コレクション間で各構造体データのインスタンスが転送される。そのためメモリ内でインスタンスのコピーが何度も行われるということも問題だ。

 「返ってきたリスト(=List<T>コレクション)に何かをしたい」というケースのほとんどで、「中間に無駄に取られるリスト」と「値型データの転送」によるパフォーマンス・ロスの兆候がある。実際には、パフォーマンス・ロスの問題があるかどうかをプロファイリングを行ったうえで次の作業を行うべきだが、本稿では上記コードでその問題が発生しているものと仮定して話を進めよう。

 この問題への対策方法の1つとして、List<T>クラスに次のようなシグネチャのメソッドを追加することが考えられる。

void ConvertIf<U>( Predicate<T> pred, Converter<T, U> conv )

 確かにこれによりPredicate<T>デリゲートで条件を確認しながら変換できるようになるだろう。しかし実際にはList<T>クラスの派生クラスを作って拡張するのは困難な道のりであり、この方法は最善の解決策ではない。

 なぜなら、FindAllメソッドやConvertAll<U>メソッドの戻り値はList<T>コレクションやList<U>コレクションを返す。よって、これらのメソッドの実装コードの中には「new List<T>」というList<T>コレクションのインスタンスを生成するコードが存在しているのである。つまり派生クラスでMyList<T>クラスを作った場合に、その派生クラスでこれらのメソッドを置き換えなければ(MyList<T>コレクションではなく)List<T>コレクションが戻り値として返されることになる。いわゆる、戻り値による「基底の暴露」(基底クラスのインスタンスが派生クラス側に表出してしまうこと)が発生するのである。一般に、クラスのメソッドの戻り値や出力パラメータなどでそのクラスのインスタンスを返す場合(つまり、「A」クラスの中で「A」クラスのインスタンスを生成してそれを戻り値や出力パラメータで返す場合)、それを継承した派生クラスで基底の暴露が発生する。

 基本クラスのFindAllメソッド内で行われている「List<T>コレクション」のインスタンス生成を「MyList<T>コレクション」のインスタンス生成に置き換えるにはFindAllメソッドの再実装が必要となる。FindAllメソッドやConvertAll<U>メソッドなどを派生クラス側に再実装していくとList<T>クラスそのものを再実装することと何ら変わらなくなる。そんなことをすれば「車輪の再発明」の愚に陥ることは明白だ。ジェネリックを使った場合に限ったことではないが、コレクション・クラスは基本的に継承して拡張することが容易でないということを覚えておいてほしい。

 このような拡張を行いたくなった場合の1つの解決策は、ヘルパー・クラスを導入することである。


 INDEX
  [特集] C# 2.0新機能徹底解説(前編)
  開発生産性を飛躍的に高めるジェネリック
    1.ジェネリックとは?
    2.ジェネリック・コレクションの利用
  3.汎用アルゴリズムを実装しているList<T>クラスのメンバ
    4.ジェネリックを使ったコレクション・クラスの拡張
  [特集] C# 2.0新機能徹底解説(後編)
  進化したC# 2.0の状態管理、匿名メソッドとイテレータ
    1.匿名メソッドとその正体
    2.イテレータの衝撃!
    3.イテレータを解剖する
    4.確実な後処理
 


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間