4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

C# dynamic型の罠:マメにキャストして型を明示しないと・・・コンパイル時の型チェックが甘くなる&実行時の処理が増える

Last updated at Posted at 2020-08-29

dynamic型を否定するつもりは全くありません。
dynamic型が絡むメソッドの戻り値とかで、型が決まっていてキャストできるものはキャストしておくとメリットがあるよ」という話です。

経緯

C#におけるdynamic型は、COMオブジェクトを操作するときなどに割とお世話になります。
今回、dynamic型を使って起動中のエクスプローラから情報を取得するようなコードを書きました。

ソースコード(NG)

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で実行ファイルのコードを確認してみました。

DynamicTest.cs

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 に置換してあります。)
image.png

ちゃんとは追ってないですが、右側は、リフレクションによるメソッド呼び出しをしていそうなコードがあります。
image.png

まとめ - 明示的にキャストするメリット/デメリット

メリット

  • コンパイル時に型チェックがされ易くなる。
  • 実行ファイルのサイズが小さくなる。
  • 実行時間が短くなる。

繰り返し処理の中であれば、キャストすることで処理時間を大きく改善できるケースもありそうな気がします。

デメリット

  • キャストの分のコードが増えるので、可読性が下がる懸念がある。
  • キャストする型を間違えた場合、結局コンパイル時には検出できずに実行時にエラーとなる可能性がある。(キャストしたあとの変数が使われるときに、たまたまアンマッチにならなかった場合など。)(デメリットというよりは、過信禁物という感じ。)

dynamic型に関する参考サイト

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?