ささいなことですが。

Windowsアプリテスト自動化ライブラリFriendly開発者の日記です。

dynamicで、あれ?って思うこと

Frienldyはdynamicを使うライブラリです。
で、dynamicって書いててちょっと直感的でない時があるんです。
よく考えると、「そうだよね。」ってなるんですけど、僕もハマったので書いておきます。

引数に渡すと、対象の関数まで動的になる

例えば、こんなメソッドがあったとします。

IEnumerable<int> Func(params int[] val)
{
    return val;
}

まずは、普通に使ってみましょう。もちろんコンパイル通ります。

Func(3, 2, 1).Where(e => e == 2);

で、引数にdynamicを混ぜてみます。で、ビルドすると・・・

dynamic val = 0;
Func(3, 2, 1, val).Where(e => e == 2);

No...コンパイルエラーです。
しかも、意味わからん感じです。
f:id:ishikawa-tatsuya:20150516112556p:plain

ちょっとWhereの前の.でインテリセンスを見てみましょう。
f:id:ishikawa-tatsuya:20150516112827p:plain
メソッドが動的になっていますね。

最初は、「なんでやねん!」って思いました。
でもこうならざるを得ないんですよね。

オーバーロードの解決が出来ないから

言われてみると、「確かにね」なんですね。
例えば、以下の二つの関数があるとき・・・

int Func(int val)
{
    return val;
}
string Func(string val)
{
    return val;
}

これは実行時まで戻り値の型が分からないんですね。
だから var って書いてますけど ret は dynamic型 なのです。

void Call(dynamic val)
{
    //実行するまで戻り値わからんやん・・・
    val ret = Func(val);
}

型変換

もう一つハマったのが型変換です。
DynamicObjectと言うのがあって、これを継承したクラスをdynamicに入れると、動的な挙動をコントロールできるというものです。
で、TryConvertをoverrideすれば型変換に関する処理をカスタマイズできます。
例えば、シンプルな例で、intにキャストすると10になるというクラスを書いてみます。

public class MyDynamic : DynamicObject
{
    public override bool TryConvert(ConvertBinder binder, out object result)
    {
        result = (int)10;
        return true;
    }
}

これは、もちろん変換できます。

dynamic d = new MyDynamic();
int i = (int)d;
int ii = d;

なのに、関数の引数に渡す時はダメなんですよね。なんでー?

int Func(int val) { return val; }

void Call()
{
    dynamic d = new MyDynamic();
    //例外が発生する・・・
    //型 'Microsoft.CSharp.RuntimeBinder.RuntimeBinderException' の例外が System.Core.dll で発生しましたが、ユーザー コード内ではハンドルされませんでした
    Func(d);
}

まあ、多分これもオーバーロードと絡んでて、複数の変換の可能性を解決できないからでしょうね。残念・・・

追記

オーバーロードとオーバーライドって、いつもどっちがどっちか分からなくなりますよね・・・

参考書籍

C#のdynamicの本と言えばこれですね。
僕もFriendly実装する時に勉強させていただきました。gihyo.jp