連載:C# 2.0入門

第2回 ジェネリック

株式会社ピーデー 川俣 晶
2007/06/29
Page1 Page2 Page3 Page4

ジェネリック・コレクションの使い方

 ジェネリック・コレクション(=ジェネリックの機能を使用しているコレクション・クラス)の使い方は、難しいものではない。

 基本的な使い方だけに限れば、たった1つ、次の原則だけを覚えればよい。

  • ジェネリック・コレクションの型名の後に、扱うデータの型名を「<」と「>」で囲んで記述する(複数のデータ型を扱う場合にはカンマで区切る)

 このようにして記述した文字列を、独立した型名と同じように書けばよい。

 つまり、文字列をListクラスに格納する場合は、

List<string>

という文字列を型名のように使えばよい。「new ArrayList()」が有効であるのと同じように、「new List<string>()」は有効である。

 Dictionaryクラスのように、キー(key)と値(value)の2つの型を用いるクラスの場合は、<>内で型を2つ指定する。キーが整数で値が文字列なら、

Dictionary<int, string>

と記述する。あとは、従来のコレクションとほとんど扱いに差はない。以下はこれらのクラスの使用例である。

using System;
using System.Collections.Generic;

class Program
{
  static void Main(string[] args)
  {
    // 文字列を要素として扱うListクラス
    List<string> list = new List<string>();
    list.Add("Sample List");

    Console.WriteLine(list[0]); // 出力:Sample List

    // キーが整数で値が文字列の要素を扱うDictionaryクラス
    Dictionary<int, string> dic = new Dictionary<int, string>();
    dic[0] = "Sample Dictionary";

    Console.WriteLine(dic[0]); // 出力:Sample Dictionary
  }
}
リスト4 ListクラスとDictionaryクラスの使用例

 もちろん、「<」と「>」で囲んで指定した型は、その型だけでなくその型を継承した型も受け入れる。インターフェイスを指定した場合は、そのインターフェイスを実装した型のインスタンスを受け入れる。

 基底クラスの型のコレクションに、それを継承した型のインスタンスを格納した例と、インターフェイスのコレクションにそれを実装したクラスのインスタンスを格納した例を以下に示す。このあたりは、C#のほかの機能と同様であり、難しいことはないだろう。

using System;
using System.Collections.Generic;

// インターフェイスの定義
public interface ISample
{
  void SayISample();
}

// 抽象クラスの定義
public abstract class SampleBase
{
  public abstract void SaySample();
}

// SampleBaseクラスを継承し、ISampleインターフェイスを実装したクラス
public class Sample : SampleBase, ISample
{
  public override void SaySample()
  {
    Console.WriteLine("Sample!");
  }
  public void SayISample()
  {
    Console.WriteLine("ISample!");
  }
}

class Program
{
  static void Main(string[] args)
  {
    // SampleBase型のコレクションにSampleオブジェクトを追加
    List<SampleBase> list1 = new List<SampleBase>();
    list1.Add(new Sample());

    list1[0].SaySample(); // 出力:Sample!

    // ISample型のコレクションにSampleオブジェクトを追加
    List<ISample> list2 = new List<ISample>();
    list2.Add(new Sample());

    list2[0].SayISample(); // 出力:ISample!
  }
}
リスト5 継承とインターフェイスを使った例

ジェネリック・メソッドと型推論

 ジェネリックは、クラス以外にも使用できる。構造体やインターフェイスにも使用できるだけでなく、デリゲートやメソッドにも使用できる。

 ここでは、特にジェネリックを使用したメソッドである「ジェネリック・メソッド」に注目しよう。これは、すぐに使える便利な機能であると同時に、注意を要する面もあるためだ。

 クラス・ライブラリが提供するジェネリック・メソッドの中で、特に誰でも使う可能性があるのは、配列をソートするArrayクラスのSortメソッドだろう。使い方はクラスと同様、メソッドの名前の後ろに「<」と「>」で囲んだ型名を書くだけである。

using System;
using System.Collections.Generic;

class Program
{
  static void Main(string[] args)
  {
    int[] array = { 3, 2, 1 };
    Array.Sort<int>(array); // ジェネリック・メソッドの呼び出し

    foreach (int i in array) Console.WriteLine(i);
    // 出力:
    // 1
    // 2
    // 3
  }
}
リスト6 ジェネリック・メソッドを使う

 <int>を指定したSort<int>メソッドは、引数にint型の配列を受け取り、その要素を昇順にソートするためのメソッドだ。

 しかし、このArray.Sortの後ろにある<int>という記述はあまり意味がない。なぜなら、引数に指定した変数arrayの型がint[]型である以上、通常<int>以外を記述することはあり得ないからだ。

 そこで、「型の推論」という機能を使ってC#コンパイラに型を推理させることができる。この機能があるため、<int>を省いても意図どおりに動作する。

using System;

class Program
{
  static void Main(string[] args)
  {
    int[] array = { 3, 2, 1 };
    Array.Sort(array); // <int>を省略している

    foreach (int i in array) Console.WriteLine(i);
    // 出力:
    // 1
    // 2
    // 3
  }
}
リスト7 型の推論によってジェネリック・メソッドを使う

 このコードは、一見するとC# 1.x時代のジェネリック・メソッドでないSortメソッドの呼び出しと区別がつかない。

 しかし、ildasm.NET Reflectorで実際に生成されたコードを見ると、ジェネリックではない従来版Sortメソッドではなく、型の推論が行われてSort<int>メソッドが呼び出されていることが分かるだろう。

 さて、型の推論は強力ではあるが、まれに型の推論を使えない場合もある。

 例えば、異なるIComparableインターフェイスを実装した基底クラスと派生クラスがある場合、それを明示的に使い分けねば結果が変わってしまうことがある。実際にコーディングしたのが以下の例である。

using System;

public class ClassA : IComparable<ClassA>
{
  public int Value;
  public ClassA(int v)
  {
    Value = v;
  }

  public int CompareTo(ClassA other)
  {
    return Value.CompareTo(other.Value);
  }
}

public class ClassB : ClassA, IComparable<ClassB>
{
  public ClassB(int v) : base (v) { }

  public int CompareTo(ClassB other)
  {
    return other.Value.CompareTo(Value);
  }
}

class Program
{
  static void Main(string[] args)
  {
    ClassB[] array = {
      new ClassB(3),
      new ClassB(2),
      new ClassB(1),
    };

    Array.Sort<ClassA>(array);
    foreach (ClassB b in array) Console.WriteLine(b.Value);
    // 出力:
    // 1
    // 2
    // 3

    Array.Sort<ClassB>(array);
    foreach (ClassB b in array) Console.WriteLine(b.Value);
    // 出力:
    // 3
    // 2
    // 1
  }
}
リスト8 型の推論が使えない例

 このプログラムにおいて、型の指定を省略して「Array.Sort(array);」と記述すると「Array.Sort<ClassB>(array);」と推論されるが、その実行結果は「Array.Sort<ClassA>(array);」の実行結果とは異なるものになる。


 INDEX
  C# 2.0入門
  第2回 ジェネリック
    1.ジェネリックとは何か?/新しいコレクションの紹介
  2.ジェネリック・コレクションの使い方/ジェネリック・メソッドと型推論
    3.HashtableクラスとDictionaryクラスの非互換性/ジェネリックなクラスを自作する
    4.制約の付いたジェネリックなクラス/C++のtemplate機能との相違
 
インデックス・ページヘ  「C# 2.0入門」


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

本日 月間