※dynamic
型を否定するつもりは全くありません。
「dynamic
型が絡むメソッドの戻り値とかで、型が決まっていてキャストできるものはキャストしておくとメリットがあるよ」という話です。
経緯
C#におけるdynamic
型は、COMオブジェクトを操作するときなどに割とお世話になります。
今回、dynamic
型を使って起動中のエクスプローラから情報を取得するようなコードを書きました。
ソースコード(NG)
Type comShellType = Type.GetTypeFromProgID("Shell.Application");
dynamic shell = Activator.CreateInstance(comShellType);
dynamic windows = shell.Windows();
foreach (dynamic win in windows) {
if (String.Compare(Path.GetFileName(win.FullName), "EXPLORER.EXE", true)) { // 大文字小文字無視で比較
// 中略
}
}
上記のコードでは、String.Compare
の戻り値がint
型であることをうっかり忘れていますが、コンパイルは通ります。
実行結果
ハンドルされていない例外: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 型 'int' を 'bool' に暗黙的に変換できません
場所 CallSite.Target(Closure , CallSite , Object )
場所 System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0)
String.Compare
の戻り値がint
型なので、if
文内でbool
型へのキャストをしようとして例外が発生しました。
わかったこと
コンパイル時に検出できないのはなぜか?
上記のコードでは、String.Compare(String, String, Boolean)
を使っています。
しかし、メソッドの引数にdynamic
型が含まれる場合、引数の型や戻り値の型は実行時に動的に決まるようです。
(つまり、コンパイル時には、bool
型かもしれないとして扱われ、if
文の条件式として使われていてもコンパイルエラーとして扱われない。)
試しに、下記のコード
if (String.Compare(Path.GetFileName((string)win.FullName), "EXPLORER.EXE", true)) {
のように、引数を明示的にキャストしてあげると、下記のようにコンパイル時に型のアンマッチを検出できます。
xxx.cs(xx,xx): error CS0029: 型 'int' を型 'bool' に暗黙的に変換できません。
生成される実行ファイルのコードの違い
明示的にキャストする場合としない場合を、下記のコードのif
文の行を切り替えてそれぞれコンパイルし、ildasmで実行ファイルのコードを確認してみました。
using System;
using System.IO;
class DynamicTest
{
[STAThread]
static void Main(string[] args)
{
Type comShellType = Type.GetTypeFromProgID("Shell.Application");
dynamic shell = Activator.CreateInstance(comShellType);
dynamic windows = shell.Windows();
foreach (dynamic win in windows) {
// if (String.Compare(Path.GetFileName((string)win.FullName), "EXPLORER.EXE", true)==0) { // キャストあり
if (String.Compare(Path.GetFileName( win.FullName), "EXPLORER.EXE", true)==0) { // キャスト無し
Console.WriteLine("hit");
}
}
}
}
実行ファイルのコード
左:キャストあり 右:キャストなし
(余分な差異が表示されないように、IL_#### は IL_XXXX に置換してあります。)
ちゃんとは追ってないですが、右側は、リフレクションによるメソッド呼び出しをしていそうなコードがあります。
まとめ - 明示的にキャストするメリット/デメリット
メリット
- コンパイル時に型チェックがされ易くなる。
- 実行ファイルのサイズが小さくなる。
- 実行時間が短くなる。
繰り返し処理の中であれば、キャストすることで処理時間を大きく改善できるケースもありそうな気がします。
デメリット
- キャストの分のコードが増えるので、可読性が下がる懸念がある。
- キャストする型を間違えた場合、結局コンパイル時には検出できずに実行時にエラーとなる可能性がある。(キャストしたあとの変数が使われるときに、たまたまアンマッチにならなかった場合など。)(デメリットというよりは、過信禁物という感じ。)