連載:[完全版]究極のC#プログラミングChapter2 ジェネリック川俣 晶2009/08/17 |
|
|
2.9 制約の付いたジェネリックなクラス
どのような型でも受け入れるジェネリックは過剰といえるほど強力である。そのような特徴は、あくまで入れ物に徹するコレクションでは有効だが、たいていのクラスでは持て余すことになるだろう。
そこで、その過剰な強力さを制約して使いやすくする手段が用意されている。具体的には、型パラメータに「制約リスト」を付けることができるのである。制約リストには、クラスやインターフェース、コンストラクタの制約を指定することができる。
利用頻度は低いと思われるわりに長くなるので、本書では詳しい説明は割愛する。1つだけ実例を見てみよう。リスト2.12は、インターフェース制約、コンストラクタ制約の付いたMyClass<T>クラスを宣言している。
| |
リスト2.12 制約の付いたジェネリックなクラスの例 |
このリストで制約を付けているのは、
|
という部分である。whereはそれ以降が制約であることを示し、Tが制約を付ける対象の型パラメータを意味する。その後に書かれたインターフェース名のIDisposableは、型パラメータTにはIDisposableインターフェースを実装した型しか指定できないという制約を与える。続くカンマで区切られたnew()は、型パラメータTの型に引数のないコンストラクタがなければならないという制約を与える。
この制約は、単に使い方に制限を加えるだけではない。MyClass<T>クラス内で、
|
という(IDisposableインターフェースの)Disposeメソッド呼び出しを記述できるのは、必ずIDisposableインターフェースを実装しているという制約があればこそである。IDisposableインターフェースの制約がなければ、TにDisposeメソッドが存在するか否かは予測不可能であり、コンパイルエラーになる。
また、
|
を記述できるのは、コンストラクタ制約によってTには引数のないコンストラクタが存在すると制約したからである。もし、そのような制約がなければ、Tに引数のないコンストラクタがあるか否かは予測できず、コンパイルエラーになる。
制約は、ジェネリッククラスのほかに、ジェネリックメソッドに付けることもできる。
【C#olumn】C++のtemplate機能との相違
C++のtemplate機能との相違について触れておく。
C#のジェネリックはtemplateではない。表面的に似ていても、実現するメカニズムはまったく異なっている。その相違を簡単に見てみよう。
まず、C++のtemplate機能はコンパイル時にすべての仕事を完了する。
たとえば、次のようなコードがあった場合、実行ファイルの中には、CMyClass1クラスを扱うCListクラスのコードとCMyClass2を扱うCListクラスのコードの2つが書き込まれる。つまり、さまざまな型をtemplate機能で使えば使うほど、実行ファイルのサイズは大きくなる。
|
一方、C#のジェネリックは、指定された型の種類に関係なく、コンパイル時には1つのクラスしか実行ファイルに書き込まれない。
たとえば、次のようなコードがあっても、実行ファイルに書き込まれるListクラスは1つだけである。つまり、使用する型の種類が増えても実行ファイルのサイズがそれに比例して大きくなるわけではない(もし、このListクラスがクラスライブラリのListクラスなら、すでに用意されているので、あらためて実行ファイルに書き込まれることはない)。
|
そして、実行時に「ジェネリック型のインスタンス化」と呼ばれる処理が行われ、個々の型に対応する実体のある実行コードが生成される。
ただし、個別の型に対して実体が生成されるのは値型だけで、参照型についてはたった1つの実体のみが生成される。これはList<string>クラスだろうと、List<System.Drawing.Image>クラスだろうと、List<System.Windows.Forms.Form>だろうと、すべてたった1つの実体で処理されることを意味する。
なぜ、たった1つの実体でこれほど違うオブジェクトを処理できるのだろうか? その理由は、どれほどオブジェクトに違いがあっても、そのオブジェクトを指し示す「参照」はまったく同じ内部表現だからである。
つまり、C++のtemplate機能と比較したとき、C#のジェネリックには次の特徴があるといえる。
- 実行ファイルのサイズがより小さくなる
- 実行時に必要なメモリがより小さくなる
だから、恐れる必要はない。湯水のようにジェネリックを使って、より良いソースコードを書こう。
【Exercise】練習問題
ジェネリックなコレクションであるListクラスを用いてintを格納するリストを作成する場合は次のように記述する。
|
では、intとstringの双方の型を格納できるリストを作成する場合、どのように記述すればよいか?
- 型を明示せず、「List list = new List();」と書く
- 型を2つ列挙して、「List<int,string> list = new List<int,string>();」と書く
- 共通に使用できる型はobjectしかないので、「List<object> list2 = new List<object>();」と書き、intの値はボックス化して格納する
- Listクラスにintとstringを用いるという「制約」を追加する
- ジェネリックなコレクションでは実現できないので記述不可能
INDEX | ||
[完全版]究極のC#プログラミング | ||
Chapter2 ジェネリック | 1.2.1 ジェネリックとは何か? | |
2.2.2 新しいコレクションの紹介 | ||
3.2.3 新しいコレクションクラス―LinkedListクラス | ||
4.2.4 新しいコレクションクラス―SortedDictionaryクラス | ||
5.2.5 ジェネリックコレクションの使い方 | ||
6.2.6 ジェネリックメソッドと型推論 | ||
7.2.7 HashtableクラスとDictionaryクラスの非互換性 | ||
8.2.8 ジェネリックなクラスを自作する | ||
9.2.9 制約の付いたジェネリックなクラス/C++のtemplate機能との相違/練習問題 | ||
「[完全版]究極のC#プログラミング」 |
- 第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用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
|
|