前回は「LINQ(データをラクに絞る・並べる)」をやりました。

今回は、少し抽象的ですが、C#のいろんな場面で出てくるジェネリクスです。実は、もうずっと使ってきたものの正体でもあります。

ジェネリクスとは、「型」を後から指定できるようにする仕組みです。

実は、ずっと使っていた

第12回の List を思い出してください。

List<int> numbers = new List<int>();      // 整数のリスト
List<string> names = new List<string>();  // 文字列のリスト

List<int>List<string>。この < > の中に型を書く書き方、これがジェネリクスです。知らないうちに、ずっと使っていました。

List という同じ仕組みが、<int> なら整数用、<string> なら文字列用になる。ひとつの仕組みを、いろんな型で使い回せる。これがジェネリクスの正体です。

なぜ、こんな仕組みが必要なのか

もし、ジェネリクスが無かったら、どうなるか考えてみましょう。

「整数のリスト」「文字列のリスト」「Personのリスト」…。型ごとに、別々のリストを作らなければいけません。

IntList numbers;       // 整数専用リスト
StringList names;      // 文字列専用リスト
PersonList people;     // Person専用リスト
// 型の数だけ、リストを作る…?

中身(リストの仕組み)はまったく同じなのに、型が違うだけで、何種類も作るのは無駄です。

そこで、「中身の型は、使うときに決めてね」としたのが List。

List<int>      // 使うときに int を指定
List<string>   // 使うときに string を指定
List<Person>   // 使うときに Person を指定

リストの仕組みは1つ。型だけ、後から差し込む。お弁当箱でいえば、「箱の作りは同じで、何を詰めるかは後で決める」ようなものです。

自分でも作れる

ジェネリクスは、使うだけでなく、自分で作ることもできます。たとえば「何でも入る箱」を作ってみましょう。

class Box<T>
{
    public T Content { get; set; }
}

<T>T が、「後から決める型」を表します(Type の T)。

使うときに、T の中身を指定します。

Box<int> intBox = new Box<int>();
intBox.Content = 100;        // int を入れる箱

Box<string> strBox = new Box<string>();
strBox.Content = "こんにちは";  // string を入れる箱

同じ Box という仕組みが、<int> なら整数の箱、<string> なら文字列の箱になりました。T の部分が、指定した型に置きかわるイメージです。

なぜ、型を分ける必要があるのか

「何でも入る箱なら、object 型を使えばいいのでは?」と思うかもしれません(C#には「何でも入る」型もあります)。

でも、それだと型のチェックが効かなくなります。第3回でやった「箱の種類が合わないとエラー」という安全装置が、働かなくなるんです。

Box<int> intBox = new Box<int>();
intBox.Content = "文字";   // int の箱に文字!→ エラーで気づける

ジェネリクスなら、「int の箱」と決めた以上、文字を入れようとした瞬間にエラーで教えてくれます。型の安全性を保ったまま、使い回せる。これがジェネリクスの一番の価値です。

どこで出会うか

ジェネリクスは、C#のあちこちで顔を出します。これまで出てきたものだけでも、

  • List<T>(第12回)… リスト
  • Dictionary<TKey, TValue>(第14回)… 辞書
  • int? … 実は Nullable<int> というジェネリクス(第28回)

< > を見かけたら、「ああ、型を後から決めるやつだな」と思えば大丈夫。自分でゼロから作る機会は、最初は少ないかもしれません。でも「List はジェネリクスだったのか」と分かるだけで、C#の見え方が変わります。

つまずきポイント

初心者が戸惑うのは、<T>T という記号です。

T は、ただの「仮の名前」です。「ここに、後で本物の型が入りますよ」という、場所取りのようなもの。T でなくても TItem でも構いませんが、慣習として T がよく使われます。

T = まだ決まっていない型の、仮の名前」。そう思えば、難しくありません。Box<int> と書いた瞬間、T がぜんぶ int に置きかわる、とイメージしてください。

まとめ

ジェネリクスは、型を後から指定できる仕組み。

List<int> numbers;        // 使うとき:型を指定
List<string> names;

class Box<T>              // 作るとき:T で場所取り
{
    public T Content { get; set; }
}
  • < > の中に型を書くのが、ジェネリクス
  • ひとつの仕組みを、いろんな型で使い回せる
  • 型のチェック(安全性)は、保たれたまま
  • T は「後で決める型」の仮の名前

次回は、ぐっと実務的に。ファイルの読み書き をやります。

(補足:<T> を使って、複数の型を共通の仕組みで扱える点は、第26回のインターフェースとも相性がよく、組み合わせるとさらに強力になります。今は「型を使い回す仕組み」とだけ押さえておけば十分です。)