#はじめに
Linq to Object を使用していて
「エラー ラムダ式を最初にデリゲート型または式ツリー型にキャストせずに、動的にディスパッチされる操作の引数として使用することはできません」
とエラーになってしまう原因と対策の例です。
#理由と対策
あなたのコードで、おそらくdynamic型を使用しておられると思います。
で、きっとdynamic型に(配列などの)列挙可能な型が入ることを想定してコーディングをしている個所があり、それに対してLinqでの集合操作をしているのでは...当たっています??
// 例
class AnyClass
{
private void Caller()
{
var arg = new int[] { 100, -30, 85 };
var result = Callee(arg);
Console.WriteLine(result);
}
private string Callee(dynamic s)
{
return string.Join(", ", s.Select(e => e.ToString())); // おそらくここにナミナミが...
}
}
もしそうでしたら
return string.Join(", ", s.Select(e => e.ToString())); // おそらくここにナミナミが...
の個所を
return string.Join(", ", Enumerable.Cast<object>(s));
または
return string.Join(", ", s);
とすることで、ナミナミが消えた(エラーが解消された)と思います。
###エラーの意味するところ
ラムダ式は、匿名メソッドの呼び出し(「デリゲート型」)をもっと簡略化させた「糖衣構文」としての存在意義だけではなく、処理速度がデリゲート型より一般的に高速な「式ツリー型」として解釈できる場合にはこちらを採用してくれるメリットがあります。今回のエラーは、Callee メソッドの仮引数 s が曖昧すぎて、コンパイラにとってどちらを採用したらよいのか不明なので指定してくれ、とのエラーです。
(「式ツリー」については、個人的に学習中...AI の一流儀である「決定木」が思い浮かぶ?...完全な匿名メソッドではコードの記述そのものの処理が毎回(最適化はあるにせよ)呼び出されるわけですが、式ツリーでは、コードの「式」をツリー状に分解して値を返す要領で、事前にできるだけ解釈されたコードが生成され高速になります。System.Linq.Expressions 名前空間には、そのためのたくさんのクラスが用意されています)
ラムダ式の箇所を匿名メソッドとして明示し
return string.Join(", ", s.Select(new Func<dynamic, string>(e => e.ToString())));
や
return string.Join(", ", s.Select(new Func<object, string>(e => e.ToString())));
と記述すると、コンパイルに関するこのエラーは消えます。
ただし、実行させると「'System.Array' に 'Select' の定義がありません」とエラーになってしまいます。上記のコードでは Caller メソッド内で、int配列型を渡して Callee メソッドを呼び出していますが、配列型はそのままLinqの集合演算に渡せない(配列型にSelect拡張メソッド は無い)からです。
###dynamic型
dynamic型は配列型も含めてどんな型でも引き受けてくれます(COM オブジェクトとやりとりするアプリケーションでは dynamic型 の使いまわしがよく、多用してしまったりしてしまいますが...)「どんな型でも」ですから、列挙可能(IEnumerable インターフェイス をインプリメントしている)でない型も入れることが出来るのです。
IEnumerable インターフェイスを含む型にキャストして
return string.Join(", ", Enumerable.Cast<dynamic>((System.Collections.IEnumerable)s).Select(new Func<dynamic, string>(e => e.ToString())));
とすると、実行時もエラーにならず、結果が返されます。
オチとしては、今回 string.Join メソッド の第二引数へ string[] 型で渡すために、 Linq の Select メソッドを使用しました。
しかし、Join メソッド第二引数に object[] 型を渡せば、メソッド内で配列内の要素それぞれを文字列化(object.ToString メソッド?)してくれることが判明、当初のコードにある Select メソッドの呼び出し自体が不要でした。つまるところ
return string.Join(", ", s);
だけで済んだのです...ただしこのようにした場合は Callee 側で、引数に入れられた値の型が string.Join メソッドの第二引数に相応しいかどうか(すなわち object[] 型にキャスト可能か)、次のようなガード節を作るべきでしょう。
// 例
if(!(s is System.Array)) {
throw new System.ArgumentException("s には配列型を指定する必要があります");
}
個人的に、メソッドの使い方を知らなかったことが、Linq の仕組みと式ツリーとを学ぶ機会となる、ありがたい副作用をおこしてくれました。。