Cutting Edge
C# 4.0 の dynamic キーワードの使用
Dino Esposito
静的型チェックの導入は、プログラミング言語の歴史において重要な意味がありました。1970 年代、Pascal、C などの言語は、静的な型の使用と厳密な型チェックを求めるようになりました。静的型チェックでは、適切な型のメソッド引数を渡さないで呼び出しを行うとコンパイル エラーになります。同様に、型のインスタンスに存在しないメソッドを呼び出そうとしても、コンパイル エラーが発生することは想像が付くでしょう。
ここ数年、これとは対照的な方法 (動的型チェック) を推進する別の言語が開発されています。動的型チェックは、変数の型はコンパイル時に静的に決まっていなければならず、変数がスコープ内にある限りその型を変更できないという考え方と対極をなしています。ただし、動的型チェックでは、一見同じような型であっても、自由に混在させてよいというわけではないことに注意してください。たとえば、動的型チェックを使用する場合でも、ブール値を整数に加算することはできません。動的型チェックを使用するうえで異なるのは、型チェックがプログラムのコンパイル時ではなく、実行時に行われることです。
静的型指定または動的型指定
Visual Studio 2010 と C# 4.0 には、dynamic という新しいキーワードが用意されています。これを使用して、従来は静的に型指定していた言語でも、動的に型指定できます。今回は C# 4.0 の動的側面について説明しますがその前に、基本的な用語についていくつか紹介しておきましょう。
まず、変数を、特定の型に限定される値の格納場所と定義します。次に、静的に型指定される言語の 4 つの基本特性を以下のように規定します。
- 式の型はコンパイル時に認識される
- 変数の型はすべてコンパイル時に認識される
- コンパイラは、式が変数に代入する際の型制限と、変数の型制限が一致することを保証する
- 構文解析タスク (オーバーロードの解決など) はコンパイル時に行われ、その結果はアセンブリに書き込まれる
動的言語には、これとは正反対の特性があります。式の型はコンパイル時には認識されません。変数についても同様です。格納に関する制限は実行時に確認され、コンパイル時には確認されません。構文解析は実行時にのみ行われます。
静的に型指定される言語でも、操作によっては動的に実行することができます。キャスト演算子が存在するため、型変換を実行時の操作として実行できます。変換はプログラム コードの一部となり、キャスト演算子によって表されるこの構文は「実行時にこの変換の妥当性を動的にチェックする」と要約することができます。
ただし、動的、静的 ("厳密"、"柔軟" と言い換えてもよいかもしれません) といった属性は、最近では、言語全体ではなく、プログラミング言語の個々の機能に当てはめる方が望ましいと考えられています。
ここで、Python と PHP について少し考えてみましょう。どちらも動的言語で、変数を使用でき、格納されている実際の型をランタイム環境で認識することができます。ただし、PHP では、同じスコープ内の同じ変数に、たとえば整数と文字列を格納できます。この点から見ると、PHP は (JavaScript のように) 柔軟に型指定される動的言語です。
一方、Python では、変数の型を設定する機会が 1 回しかありません。そのため、より厳密に型指定されると言えます。開発者は型を変数に動的に割り当てることができ、ランタイムは代入されている値から型を推測できます。ただし、いったん型を割り当てると、その変数に不適切な型の値を格納することはできません。
C# の動的な型
C# 4.0 には、動的にも静的にも、つまり柔軟にも厳密にも型指定できる機能が用意されています。C# は静的に型指定される言語として開発されましたが、dynamic キーワードを使用すればどのような状況でも、次の例のように、動的に型指定することができます。
dynamic number = 10;
Console.WriteLine(number);
また、dynamic は予約キーワードではなくコンテキスト キーワードなので、dynamic という名前が付けられた既存の変数やメソッドがあってもそのまま使用されます。
C# 3.0 では var、ラムダ、またはオブジェクト初期化子を使用しなくてもよかったように、C# 4.0 でも dynamic を必ずしも使用しなくてもかまわないことに注意してください。C# 4.0 には、いくつか既知のシナリオをより簡単に処理できるよう特別に、新しい dynamic キーワードが用意されています。より効率的な方法で動的オブジェクトを操作できる機能が追加されましたが、言語自体は基本的には静的に型指定される言語です。
では、なぜ動的オブジェクトを使用する必要があるのでしょう。まず、開発者が処理するオブジェクトの型を把握していない場合があります。特定の変数を静的に型指定する際、手掛かりはあっても確実ではないかもしれません。これは、COM オブジェクトを操作する場合、インスタンスを取得するためにリフレクションを使用する場合など、多くの一般的状況であり得ることです。このような場合、dynamic キーワードを使用した方が、状況によっては処理する際の手間が省けます。dynamic を使用すると、読み書きしやすいコードとなり、アプリケーションを理解しメンテナンスするのが容易になります。
次に、オブジェクトが本質的に変化する性質を備えている場合があります。IronPython、IronRuby など、動的プログラミング環境で作成されたオブジェクトを操作する場合があるでしょう。また、この機能は、HTML DOM オブジェクト (expando プロパティの影響を受けます) と動的性質を持つように特別に作成された Microsoft .NET Framework 4 オブジェクトを併用することも考えられます。
dynamic を使用する
C# 型システムでは dynamic は型であるという考え方を理解しておくことが重要です。dynamic にはきわめて特殊な意味がありますが、これは間違いなく型であると考えて処理することが大切です。dynamic は、宣言する変数の型、コレクション内の項目の型、またはメソッドの戻り値の型として指定できます。また、dynamic をメソッド パラメーターの型として使用することもできます。しかし、dynamic を typeof 演算子と組み合わせて使用したり、クラスの基本型として使用することはできません。
以下のコードに、メソッドの本文で dynamic 変数を宣言する方法を示します。
public void Execute() {
dynamic calc = GetCalculator();
int result = calc.Sum(1, 1);
}
GetCalculator メソッドから返されるオブジェクトの型を十分把握していれば、その型の calc 変数を宣言したり、変数を var として宣言して、コンパイラが詳細を正確に把握できるようにすることも可能です。ただし、var または明示的な静的型を使用すると、Sum メソッドは、GetCalculator メソッドから返す型が公開するコントラクトに存在する必要があります。メソッドが存在しないと、コンパイル エラーが発生します。
dynamic では、式の正確性に関する判断はすべて実行時まで遅延されます。コードはコンパイルされ、Sum メソッドが calc 変数に格納された型で使用可能であれば実行時に解決されます。
このキーワードを使用して、クラスのプロパティを定義することもできます。このためには、public、protected、static など、必要な可視性修飾子を使用して、メンバーを修飾します。
図 1 は dynamic キーワードの多用性を示しています。メイン プログラムでは、呼び出した関数の戻り値を利用してインスタンスが作成される、dynamic 変数を使用しています。関数が動的オブジェクトを受け取って返すのでなければ、大した問題ではないでしょう。次の例に数値を渡し、関数内で 2 倍にすると何が起こるか興味深いので確認してみましょう。
図 1 関数のシグネチャで使用される dynamic
class Program {
static void Main(string[] args) {
// The dynamic variable gets the return
// value of a function call and outputs it.
dynamic x = DoubleIt(2);
Console.WriteLine(x);
// Stop and wait
Console.WriteLine(“Press any key”);
Console.ReadLine();
}
// The function receives and returns a dynamic object
private static dynamic DoubleIt(dynamic p) {
// Attempt to "double" the argument whatever
// that happens to produce
return p + p;
}
}
値 2 を渡してこのコードを実行すると、値 4 を受け取ります。2 を文字列として渡すと、22 を受け取ることになります。この関数では、実行時のオペランドの型に基づいて、+ 演算子が動的に解決されます。型を System.Object に変更すると、+ 演算子は System.Object では定義されないためコンパイル エラーが発生します。dynamic キーワードを使用すると、dynamic キーワードなしでは不可能だったシナリオを実現できます。
dynamic と System.Object
.NET Framework 4 までは、共通基本クラスを使用した場合のみ、状況に応じてメソッドから異なる型を返すことができました。おそらくこの問題の解決には System.Object を使用したでしょう。System.Object を返す関数を使用すると、呼び出し元は、ほぼすべての型にキャストできるインスタンスを受け取ることができます。では、System.Object ではなく dynamic を使用すると、どのような点が優れているのでしょう。
C# 4 では、dynamic を宣言した変数に格納されている実際の型は実行時に解決されます。コンパイラでは単純に、dynamic を宣言した変数のオブジェクトはあらゆる操作をサポートしていると想定します。つまり、次の例のように、実行時にはオブジェクトが存在すると想定して、オブジェクトのメソッドを呼び出すコードを作成することができます。
dynamic p = GetSomeReturnValue();
p.DoSomething();
C# 4.0 では、このようなコードでコンパイル エラーが発生することはありません。System.Object を使用すると、似たようなコードでもコンパイルできないため、機能するように独自の処理 (リフレクション、大胆なキャストなど) が必要になります。
var と dynamic
var キーワードと dynamic キーワードは、一見同じように見えます。var は、変数の型を、コンパイル時の初期化子の型に設定する必要があることを示しています。
一方、dynamic は、変数の型が C# 4.0 で使用できる動的型であることを示しています。つまり、dynamic と var にはまったく正反対の意味があります。var を使用すると、静的型指定が強化および向上します。これは、初期化子から返される実際の型を確認しているコンパイラが、変数の型を推測できるようにすることが目的です。
dynamic キーワードを使用すると、静的型指定を完全に回避します。dynamic を変数宣言に使用すると、コンパイラに変数の型を一切推測しないように指示します。型は、実行時に認識する必要があります。var を使用すると、変数宣言で明示的に型を指定する従来の手法を選択した場合と同様に、静的に型指定されるコードになります。
2 つのキーワードのもう 1 つの違いは、var はローカル変数宣言内でのみ有効である点です。var を使用すると、クラスのプロパティを定義することも、関数の戻り値やパラメーターを指定することもできません。
開発者は、COM や DOM API から返されるオブジェクト、動的言語 (IronRuby など)、リフレクション、または新しい拡張機能を使用して C# 4.0 で動的に作成されたオブジェクトから受け取るオブジェクトなど、型が不確定なオブジェクトを含む可能性がある変数を、dynamic キーワードと併用できます。
ただし、動的型でも型チェックは省略されません。型チェックがすべて実行時に行われるようになっただけです。実行時に型の互換性に関する問題が検出されると、例外がスローされます。
Dino Esposito は、『Programming ASP.NET MVC』(Microsoft Press) の著者であり、『Microsoft .NET: Architecting Applications for the Enterprise』(Microsoft Press、2008 年) の共著者でもあります。Esposito はイタリアに在住し、世界各国で開催される業界のイベントで頻繁に講演しています。ブログは weblogs.asp.net/despos (英語) です。
この記事のレビューに協力してくれた技術スタッフの Eric Lippert に心より感謝いたします。