.NET を理解する4
ジェネリックを理解する
.NET Framework 2.0 から追加された機能です。
主にコレクションで良く見られますが、プログラムの設計においてもタイプセーフでかつ汎用性の高いオブジェクトを作成することができます。
論より Run
以下で簡単な Generic の実装サンプルを示します。
ホテルルームは基本的に何でも入ります。ここでは、人間がチェックイン可能なホテルルームを作ります。
class Program { static void Main(string[] args) { HotelRoom<Human> room = new HotelRoom<Human>(); room.CheckIn(new Human("azalea", true)); } } public class Human { private string name; private bool ismale; public Human(string name, bool isMale) { this.name = monsterName; this.ismale = isMale; } public bool IsMale { get { return ismale; } } public string Name { get { return this.name; } } } class HotelRoom<T> { private T customer; public void CheckIn(T someone) { this.customer = someone; } public bool IsThere() { return customer != null; } }
このとき、room は Human で型が決定されています。
すると、このような代入はできなくなります。
HotelRoom<Human> room = new HotelRoom<Human>(); room.CheckIn("liliy");
上記は、異なる型を挿入したことでコンパイルエラーになります。
これはソースを記述する際に取り込む型を決定せず、実行時に型を決定することができる仕組みです。
もちろんオブジェクト指向に則るコーディングも可能ではありますが、初期のクラス設計時点ではまだしも、後からクラスを追加する際に不便な場合もあります。そんなときに使える機能です。
しかし、このままでは実は危険ですね。
class Program { static void Main(string[] args) { HotelRoom<Monster> room = new HotelRoom<Monster>(); room.CheckIn(new Monster("Zombie")); } } public class Human { private string name; private bool ismale; public Human(string name, bool isMale) { this.name = monsterName; this.ismale = isMale; } public bool IsMale { get { return ismale; } } public string Name { get { return this.name; } } } public class Monster { private string name; public Monster(string monsterName) { this.name = monsterName; } public string Name { get { return this.name; } } } class HotelRoom<T> { private T customer; public bool CheckIn(T someone) { this.customer = someone; } public bool IsThere() { return customer != null; } }
なんということでしょう、ホテルにゾンビがチェックインしてきました!
これではこのホテルでバイオハザードが起きてしまいます(笑
そこで、Generic を指定する際に、制限を設けます。
class Program { static void Main(string[] args) { HotelRoom<Programmer> room = new HotelRoom<Programmer>(); room.CheckIn(new Programmer("azalea", true)); } } public class Programmer : Human { public Programmer(string name, bool isMale) : base(name, isMale) { } public void WriteCode() { // なんらかの処理 } } public class Human { private string name; private bool ismale; public Human(string name, bool isMale) { this.name = name; this.ismale = isMale; } public bool IsMale { get { return ismale; } } public string Name { get { return this.name; } } } class HotelRoom<T> where T : Human { private T customer; public void CheckIn(T someone) { this.customer = someone; } public bool IsThere() { return customer != null; } }
「where T : Human」の記述によって、このジェネリッククラスは Human かそれを継承するクラスしか指定できなくなりました。
これでモンスターは入ってこれません。
実はこの記述には、もうひとつの意味があります。制限を課したことで、「少なくとも T で入ってくるのは Human である」ということです。
これにより、以下のような記述を行うことができます。
class HotelRoom<T> where T : Human { private T customer; public void CheckIn(T someone) { this.customer = someone; } public string RoomCustomer() { // customer は Human なので、Name がある if (this.customer != null) return this.customer.Name; return null; } public bool IsThere() { return customer != null; } }
これで結構使い道があるかと思います。
他の制限としては
- 「where T : struct」構造体である。
- 「where T : class」参照型である。
- 「where T : new()」引数なしのコンストラクタを持つ。
- 「where T : [interface]」[interface]を継承する。
等が使えます。*1
Generic は基本的に型宣言で使うことが多いですが、メソッドにも指定することができます。
Dictionary<string, T> getNameMap<T>(T[] args)
{
...
}
*1:殆どの場合、クラス制限とインターフェース制限だと思います。