特集

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

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

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

 ヘルパー・クラスを使って拡張すると、具体的には以下のようなコードになる。

public static class ConvertIfHelper<T,U>
{
  private class InnerConvertIf
  {
    public List<U> temp = new List<U>();
    Predicate<T> pred;
    Converter<T, U> cvtr;
    internal InnerConvertIf(Predicate<T> pred,Converter<T, U> cvtr)
    {
       this.pred = pred;
       this.cvtr = cvtr;
    }
    public void ConvertIfAction(T that)
    {
      if(pred(that)) temp.Add(cvtr(that));
    }
  }

  static public List<U> ConvertIf(List<T> from, Predicate<T> pred, Converter<T, U> cvtr)
  {
    InnerConvertIf inner = new InnerConvertIf(pred, cvtr);
    from.ForEach(inner.ConvertIfAction);
    return inner.temp;
  }
}

public DeliveryOrder MakeDelivery()
{
  DeliveryOrder deliv = new DeliveryOrder();
  deliv.Items.AddRange(
  ConvertIfHelper<OrderLine,DeliveryLine>.ConvertIf(
    Items,IsReadyDeliver,ToDeliveryLine ) );
  return deliv;
}
ヘルパー・クラスにより条件判定とデータ変換処理を行うサンプル・コード

 少なくともリストが1つ余計に生成されることは防げる。さらに転送回数も減ったことでメモリ・コスト、パフォーマンスが若干改善されるだろう。

 また、Predicate<T>デリゲートやConverter<T, U>デリゲートを受け取るようにすることでヘルパー・クラス内には特定のクラスに依存した処理がなくなる。結果としてこのConvertIfHelper<T, U>クラスはどんなプロジェクトにも再利用できる。あなたのプロジェクトが前述したパフォーマンスの問題を抱えた場合にはこのクラスを再利用するとよい。

 もし似た問題に遭遇したら、もう一度以下のことを思い出してほしい。

  • コレクション・クラスを継承で拡張することは困難だ
  • クラスを継承で拡張することができない場合にはヘルパー・クラスがよい候補になる
  • Predicate<T>デリゲートやConverter<T, U>デリゲートを受け取るようにすることで、ヘルパー・クラスへの特定のクラスに依存した処理の混入が防げる
  • ヘルパー・クラスへの処理の混入を防ぐと、そのヘルパー・クラスの再利用性が高まる

■データの並べ替え:SortメソッドとComparison<T>デリゲート

 コレクションに格納されたデータの並べ替えにはSortメソッドが利用できる。SortメソッドはComparison<T>デリゲートを利用して、並べ替え順位を決定するためにデータの比較を行う。Comparison<T>デリゲートは次のように宣言されている。

public sealed delegate int Comparison<T>(T x, T y);

 Comparison<T>デリゲートは、2つのパラメータ(x,y)を受け取り、int型の戻り値の符号でデータの順序を表す。戻り値で正の値を返すとxはyより後ろに位置することを意味し、負を返すとxはyよりも前に位置することを意味する。0を返すとその順序は指定されない(つまり同一の順位であることを示す)。

 ちなみに、このSortメソッドは「安定していない」。これを聞いてびっくりする人のために注記しておくと、不安定(例えばクラッシュする)といっているわけではない。この場合の「安定していない」はソート・アルゴリズムについての用語であり、比較の結果が同一順位だった場合に、並べ替え項目のもともとの位置関係が維持される並べ替えアルゴリズムを「安定したソート・アルゴリズム」といい、位置関係が維持されないのを「安定していない」という。

 さて、単純に製品の価格順でソートするのは以下のコードで実現できる。

public void SortOrderLineByName()
{
  Items.Sort(delegate(OrderLine x, OrderLine y)
  {
    return x.prod.Price != y.prod.Price ?
        (x.prod.Price < y.prod.Price ? (-1) : (1)) : (0);
  });
}
SortメソッドとComparison<T>デリゲートを利用してデータの並べ替えを行うサンプル・コード

 複数の項目を基準にしてソートする場合には、まずは優先される項目(第1基準の項目)でデータを比較して順位を決定し、それで順位が決まらなければそれに続く第2基準の項目をチェックして順位を決めるようにする。上記のコードでは「(0)」の部分で第2基準の項目をチェックしている。順位決定のデータ比較の一種の慣用句として、上記のコードから下のようなパターンが得られる。

return
  x.A != y.A ? ( (x.A < y.A)?(-1):(1) ) :    // 第1基準のチェック
  x.B != y.B ? ( (x.B < y.B)?(-1):(1) ) :    // 第2基準のチェック
  ……
  x.Z != y.Z ? ( (x.Z < y.Z)?(-1):(1) ) :    // 第n基準のチェック
  (0); // すべてを通過すれば順位は同一

 また、ソートを逆順にするにはComparison<T>デリゲートの符号を逆転させればよい。もう少しジェネリックに親しむと(毒されてくると)、複数の項目を基準にしてソートするには複数のComparison<T>デリゲートを順次チェックするコードを使えばよいということに気づくだろう。結果として以下のようなクラスにたどり着ける。何が起こるかはこのコードを実際に動かして楽しんでほしい。

public class CompalisonBuilder<T>
{
  // 複数の項目を基準にしてソートする
  public Comparison<T> Combine(Comparison<T> before, Comparison<T> after)
  {
    return delegate(T x, T y)
    {
      int n = before(x, y);
      return (n==0) ? after(x, y) : n;
    };
  }

  // ソートを逆順にする
  public Comparison<T> Reverse(Comparison<T> that)
  {
    return delegate(T x, T y) { return -that(x, y); };
  }
}
ジェネリックを高度に活用して複雑なソートやデータ並べ替えの逆転を行うサンプル・コード

 上記サンプル・コードのように、複雑なソート条件を扱うのはDataSetクラスを使わずとも容易にできる。創意工夫のしどころだ。

■「1つのパラメータを取るpublic staticなメソッド」の勧め

 クラスを作ったら、そのクラスに対するよくある判定を、

public static bool IsXXXXX(T that)

で作成しておくことを勧める(Tは作成したクラス)。この形式のメソッドはPredicate<T>デリゲートとして即利用できるので非常に便利だ。同様に

public static U ToXXXXX(T from)

の形式はConverter<T, U>デリゲートとして即利用できる(Tは変換前のクラス、Uは変換後のクラス)。

 ジェネリック以前では「public bool IsXXXXX()」や「public bool ToXXXXX()」のようにstatic(静的メソッド)でなく、かつ、パラメータを取らない形式が推奨されてきたが、これらをPredicate<T>デリゲートやConverter<T, U>デリゲートとして利用することは若干面倒だ。ちょっとした違いだが頭を柔軟に「より使いやすい形式でメソッドを提供する」ということを考えて変化を受け入れてみよう。ジェネリックがより快適になるだろう。

■ジェネリックのまとめ

 ジェネリックでは.NET Frameworkクラス・ライブラリに組み込まれているアルゴリズムの利用法の理解と、そのうえで各種デリゲートをいかに記述するかがポイントとなる。本稿ではジェネリックのエッセンスとなるコードの提示を目標としたので、提示したコードとその動きを理解すればそれを応用する段階にすぐにでも入れるだろう。とにかくまずは使って慣れることが重要だ。

 ジェネリックについてはListコレクション以外については触れていないし、型に対する制約(constraint)の記述などにも触れていないが、ジェネリックの考え方とよくあるコーディング例は示せたと思う。それ以上の細部については、機会があればもっと長い期間をかけてじっくり説明していきたいと思う。

 ジェネリックを活用したプログラムを作成するには、各デリゲートをうまく組み合わせる方法を考えることが必要だ。そのための筆者流のエッセンスは一部盛り込むことができた。しかし、オブジェクト指向の常識を一部で破る必要が生まれてくる。「1つのパラメータを取るpublic staticなメソッド」はその一例にすぎない。ジェネリックを包含したオブジェクト指向を適切に体系付けて解説している文書は、私の知る限りでは存在しない。それは、この例のような未解決の仕様、概念レベルでの問題がまだまだ残っているからだ。

 ジェネリックを含んだオブジェクト指向については議論の余地がまだまだある。この記事は皆さんをその議論へ招待するものでもある。掲示板やメーリング・リストなどでぜひ議論してほしい。

 さて次回以降では、C# 2.0の匿名メソッド(Anonymous Methods)とイテレータ(Iterator)について解説したうえで、残りの細かい新機能について説明していきたいと思う。匿名メソッドやイテレータは非常に面白い題材だ。何が面白いかはお楽しみとして取っておくが、ILDASMで逆アセンブルして中身を見てみるなど、次回はメソッドの中にも踏み込むディープな記事となる予定だ。期待してほしい。End of Article


 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 記事ランキング

本日 月間