ページ

2004年5月21日金曜日

C# の Generics

今さらながら .NET Framework 2.0 で搭載される Generics のことです。
といっても解説じゃありません(^^;


基本的には以下のような感じですね。
(以下に書いているコードは VS2005 CTP March 2004 では動きました)



private static void TestGenerics()
{
    List<int> l = new List<int>();
    l.Add(0);
    l.Add(1);
    l.Add(2);
    l.Add(3);
 
    // 普通に Count でループ
    for (int i = 0; i < l.Count; ++i)
    {
        Console.WriteLine(l[i].ToString());
    }
 
    // もちろん foreach でもループできる
    foreach (int n in l)
    {
        Console.WriteLine(n.ToString());
    }
}


通常版では自動拡張する配列は ArrayList クラスでしたが、Generics 版は List クラスになってます(たぶん(^^; リファレンスに List が見つからないんですが)。といっても、Generic なだけで使い方は ArrayList とほとんど同じです。


ただ、ArrayList はほんとに単なる 「自動拡張する配列」 でしたが、List にはいろいろとメソッドが増えてます。たとえば、Find とか。で、おもしろいのは、この Find の引数って Inttelisense で 「Predicate match」 って出てくるんですよ。Predicate ってなにかと言うと、
  public sealed delegate bool Predicate(T obj);
という Generic な delegate なんですね。
ついでに言うと、Find の戻り値は Nullable だそうです。なるほど、たとえ T が値型だったとしても、見つからなかったときに null が返せるように Nullable になってるわけですね。


で、Find を使ったコードはというと以下のような感じ。



private static void TestGenerics()
{
    List<int> l = new List<int>();
    l.Add(0);
    l.Add(1);
    l.Add(2);
    l.Add(3);
 
    // 0 か 2 のものを探して表示
    Console.WriteLine(l.Find(IsZeroOrTwo).Value.ToString());
}
 
private static bool IsZeroOrTwo(int n)
{
    return n == 0 || n == 2;
}


ここでは、static なメソッドである IsZeroOrTwo を delegate のコールバックメソッドとして使ってます。この場合 0 と 2 の 2つが見つかりますが、Find は最初に見つかったほうだけを返すので 0 だけが表示されます。
あぁ、そうそう、検索した結果見つからなくて null が返ってきたときのチェックを省略しちゃってますが、もちろん、ほんとはすべきです。


どうでしょ?すごくイイ!と思いませんか?(私は激しく思います。色を変えたくなるくらい)
ただ、C++ の STL を知らないと Predicate の考え方がわかりにくいかもしれませんね。


もう 1つ。List.FindAll もあります。これは見つかったやつをみんな返すっていうメソッドです。なので、戻り値は List です。引数は Find と同じ。
それと ForEach っていうメソッドもあります。これの引数は 「Action action」 です。Action も、
  public sealed delegate void Action(T obj);
です。Predicate との違いは戻り値が void なところだけですね。


この FindAll と ForEach を使うと、



private static void TestGenerics()
{
    List<int> l = new List<int>();
    l.Add(0);
    l.Add(1);
    l.Add(2);
    l.Add(3);
 
    // 0 か 2 のものを探して表示
    l.FindAll(IsZeroOrTwo).ForEach(WriteLine);
}
 
private static bool IsZeroOrTwo(int n)
{
    return n == 0 || n == 2;
}
 
private static void WriteLine(int n)
{
    Console.WriteLine(n.ToString());
}


こんな感じに書けます。


さらに...
delegate は Anonymous Method として書けるはずです。ということは...



private static void TestGenerics()
{
    List<int> l = new List<int>();
    l.Add(0);
    l.Add(1);
    l.Add(2);
    l.Add(3);
 
    l.FindAll(delegate(int n)
    {
        return n == 1 || n == 3;
    }).ForEach(delegate(int n)
    {
        Console.WriteLine(n.ToString());
    });
}


っていう書き方ができるんです。


イイ!イイ!イイ!激しくイイ!
こりゃ楽しいや。

5 件のコメント:

  1. typedef 無いのがイタイなぁ。

    テンプレートを使って作った方の名前を、何度も何度も書くのはいやん ;-p

    返信削除
  2. ん? using でクラス名に別名つけられましたよね?

    テンプレートで作った型にも別名つけられるのかな?


    using IntList=List<int>;


    みたく。。。

    返信削除
  3. インライン(anonymous delegate)で書いてると、だんだんRubyに見えてきた。。。

    predicateはLISP的。

    返信削除
  4. さらにBeta1では T[]->IList<T> or T[]-> IEnumerable<T> Implicit Conversionがサポートされますので青柳さんのサンプルは

    private static void TestGenerics()

    {

    List<int> l = new List<int>(new int[]{0,1,2,3});


    l.FindAll(delegate(int n)

    {

    return n == 1 || n == 3;

    }).ForEach(delegate(int n)

    {

    Console.WriteLine(n.ToString());

    });

    }

    とかけるようになります。T[]->IList<T> Conversionについては近いうちに自分のBLOGで書こうと思っています。

    返信削除
  5. >ん? using でクラス名に別名つけられましたよね?

    >テンプレートで作った型にも別名つけられるのかな?


    >using IntList=List<int>;


    >みたく。。。


    ソースファイルごとにusingしないとだめなんですか?

    返信削除