連載:C# 2.0入門

第2回 ジェネリック

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

HashtableクラスとDictionaryクラスの非互換性

 ジェネリック・コレクションは容易に使うことができ、すぐにでも従来のコレクションから移行したいものである。

 しかし、中には(従来版からジェネリック版への)機械的な置き換えでは対処できないものもある。特に深刻なのは、HashtableクラスとDictionaryクラスの非互換動作の例だろう。実際にコードを見ていただきたい(リスト9)。

using System;
using System.Collections;
using System.Collections.Generic;

class Program
{
  static void Main(string[] args)
  {
    try
    {
      // Hashtableクラス(従来のコレクション・クラス)
      Hashtable hashtable = new Hashtable();
      Console.WriteLine(hashtable[0] == null);

      // Dictionaryクラス(ジェネリック・コレクション)
      Dictionary<int, object> dictionary
                                 = new Dictionary<int, object>();
      Console.WriteLine(dictionary[0] == null); // 例外が発生
    }
    catch (Exception e)
    {
      Console.WriteLine(e.ToString());
    }
  }
}
リスト9 HashtableクラスとDictionaryクラスの非互換動作の例

True
System.Collections.Generic.KeyNotFoundException: 指定されたキーはディレクトリ内
に存在しませんでした。
   場所 System.ThrowHelper.ThrowKeyNotFoundException()
   場所 System.Collections.Generic.Dictionary`2.get_Item(TKey key)
   場所 Program.Main(String[] args) 場所 ……\Program.cs:行 15
リスト9の実行結果

 このとおり、存在しないキーに対するアクセスは、従来のHashtableクラスではnullを返すが、新しいDictionaryクラスではSystem.Collections.Generic.KeyNotFoundException例外を投げる。

 あるキーに対する値が存在するか否かを判定する処理は割とよくあるが、もし上記のサンプル・コードのような方法で判定している場合は書き直す必要がある*

* あるキーがコレクションに含まれているかは、ContainsKeyメソッドで判定できる。また、キーの存在チェックと値の取得を1回で行う場合は、TryGetValueメソッドという便利なメソッドもある。

if (Dictionaryオブジェクト.TryGetValue(キー, out value)) { xxxx }

といった文を実行すると、キーが存在する場合にのみ変数valueに値が入り、if文の条件が成立するので、xxxxの部分にvalueの値を使うコードを安全に書くことができる。

 しかし、なぜこのような仕様変更が行われたのだろうか。よく考えると新しいDictionaryクラスの仕様は合理的であることが分かる。

 例えば、C# 2.0の新機能、「null許容型」を使うと、値としてnullも代入できるint型などを容易に使うことができる(本連載第4回で解説予定)。この型の値をコレクションに追加したとき、当然のことながら、nullも有効な値と見なされねばならない。

 これを実際に記述した例を以下に示す(「int?」はnull許容型のint型)。

using System;
using System.Collections.Generic;

class Program
{
  static void Main(string[] args)
  {
    Dictionary<int, int?> dictionary = new Dictionary<int, int?>();
    dictionary[0] = null;

    Console.WriteLine(dictionary.Count); // 出力:1
    Console.WriteLine(dictionary[0] == null); // 出力:True
  }
}
リスト10 nullが正規の値となるコレクション

 このプログラムは、コレクションに確かに1つのキーと値のペアを入れている。そのことは、dictionary.Countの値が1であることから確認できる。しかし、格納された値はnullである。

 もし、キーが存在しないときに得られる値もnullであれば、nullという値が格納されたのか、キーが存在しないのか、区別ができないことになる。キーがないときは例外を投げるという仕様は、この区別を明確につけるために必要とされる対処というわけである。

 ただし、例外は重い処理なので、例外を使うよりも上述のContainsKeyメソッドで判定する方がよいだろう。

ジェネリックなクラスを自作する

 ジェネリックはコレクションには便利だが、一般のクラスやメソッドなどで使うことはあまりない。任意の型を受け入れるという、途方もなく強力なパワーを持ったクラスを作らねばならない機会はあまり多くはない、逆にそれだけのパワーを持ったクラスを設計、実装することは一筋縄ではいかないからだ。

 しかし、技術的に見て、定義にジェネリックを使うことは簡単である。以下、ジェネリックなクラスを自作する例を見てみよう。ここでは、任意の型の変数を内部に持つだけのクラスを作成してみる。

using System;

// ジェネリックなMyClassクラスの定義(「T」は型パラメータの名前)
public class MyClass<T>
{
  public T Value;

  public MyClass(T v)
  {
    Value = v;
  }
}

class Program
{
  static void Main(string[] args)
  {
    MyClass<int> i = new MyClass<int>(0);
    Console.WriteLine(++i.Value); // 出力:1

    MyClass<string> s = new MyClass<string>("abc");
    Console.WriteLine(s.Value.ToUpper()); // 出力:ABC
  }
}
リスト11 自作ジェネリック クラス

 手順は2つしかない。

 第1の手順は、定義するクラスの名前の後ろに「<」と「>」で囲んで「型パラメータ」の名前を書き込む。ここに書き込む名前は実際に存在しない型の名前でよい。使用されるときに、この名前は実際に指定された型に置き換えられるからである。ちなみに、ここでは「T」または「Tで始まる名前(TValueなど)」を使うのが一般的である。

 第2の手順は、宣言された型パラメータの名前を、「あたかも実在する型の名前であるかのように」使ってクラスを実装する。

 例えば「public T Value;」という変数宣言は、あたかもTという型があるかのように記述しているが、実際に使われる場合にはTがintやstringに置き換えられるわけである。


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

本日 月間