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

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

# 経緯

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


## ソースコード(NG)

```csharp: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)`](https://docs.microsoft.com/ja-jp/dotnet/api/system.string.compare?view=netcore-3.1#System_String_Compare_System_String_System_String_System_Boolean_)を使っています。
しかし、メソッドの引数に`dynamic`型が含まれる場合、引数の型や<b>戻り値の型</b>は実行時に動的に決まるようです。
（つまり、コンパイル時には、`bool`型かもしれないとして扱われ、`if`文の条件式として使われていてもコンパイルエラーとして扱われない。）

試しに、下記のコード

```csharp

    if (String.Compare(Path.GetFileName((string)win.FullName), "EXPLORER.EXE", true)) {
```

のように、引数を明示的にキャストしてあげると、下記のようにコンパイル時に型のアンマッチを検出できます。


```

xxx.cs(xx,xx): error CS0029: 型 'int' を型 'bool' に暗黙的に変換できません。
```

## 生成される実行ファイルのコードの違い

明示的にキャストする場合としない場合を、下記のコードの`if`文の行を切り替えてそれぞれコンパイルし、ildasmで実行ファイルのコードを確認してみました。

```csharp: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/e0f042d2-e51e-fac1-1885-af1743a7a687.png)

ちゃんとは追ってないですが、右側は、リフレクションによるメソッド呼び出しをしていそうなコードがあります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438183/c100e8ab-f480-c12a-14d1-526980dcf4fe.png)


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

## メリット

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

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

## デメリット

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

# dynamic型に関する参考サイト

- [dynamic 型の使用 - C# プログラミング ガイド | Microsoft Docs](https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/types/using-type-dynamic)
- [dynamic - C# によるプログラミング入門 | ++C++; // 未確認飛行 C](https://ufcpp.net/study/csharp/sp4_dynamic.html)
- [【C#】 dynamic型の裏側 - Qiita](https://qiita.com/up-hash/items/2f36642ed937fd898bdb)
- [第1回 dynamic型（2/3） - ＠IT](https://www.atmarkit.co.jp/fdotnet/csharp4/csharp4_01/csharp4_01_02.html)
- [dynamic c# - Google 検索](https://www.google.co.jp/search?q=dynamic+c%23)