为何List<dynamic>调用Select时,方法引用与Lambda编译结果不同?
先看你给出的代码场景:
首先是你的静态方法:
static string ToString(dynamic d) { return (string)d.ToString(); }
然后创建了一个动态类型的列表:
var tmp = new List<dynamic> { 1, "2", 345 };
你发现两种写法的编译结果完全不同:
- 写法1(正常编译):
IEnumerable<string> test = tmp.Select(ToString); - 写法2(编译报错):
错误信息:IEnumerable<string> test = tmp.Select(x => ToString(x));CS0266 Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<dynamic>' to 'System.Collections.Generic.IEnumerable<string>'. An explicit conversion exists (are you missing a cast?)
核心原因:方法组 vs Lambda 的类型推断逻辑差异
这本质是C#编译器处理方法组转换和Lambda表达式类型推断时,对dynamic类型的处理规则不同:
方法组的情况:
当你直接把ToString方法组传给Select时,编译器会直接匹配方法的签名:你的ToString接受dynamic参数,返回string。编译器会据此推断Select的泛型参数——输入是dynamic(来自List<dynamic>),输出是string,所以Select返回的是IEnumerable<string>,刚好和左边的变量类型匹配,自然能编译通过。Lambda表达式的情况:
当你写x => ToString(x)时,x的类型是dynamic(因为它来自List<dynamic>的元素)。C#编译器有个规则:只要Lambda表达式的参数是dynamic,或者表达式内部涉及dynamic的操作,整个Lambda的返回类型会被推断为dynamic——哪怕你调用的方法明确返回string,编译器也会优先把结果标记为dynamic。这就导致
tmp.Select(x => ToString(x))的返回值是IEnumerable<dynamic>,而你要把它赋值给IEnumerable<string>,这两种类型之间没有隐式转换(dynamic到具体类型需要显式操作),所以就抛出了CS0266错误。
解决办法
如果你想用Lambda的写法,有几种方式可以绕开这个问题:
显式指定Lambda的参数类型:
把x的类型从dynamic改成具体类型(比如object),这样编译器就不会把Lambda的返回值推断为dynamic:IEnumerable<string> test = tmp.Select((object x) => ToString(x));显式指定
Select的泛型参数:
直接告诉编译器Select的输出类型是string,跳过自动推断:IEnumerable<string> test = tmp.Select<dynamic, string>(x => ToString(x));或者更简洁的(输入类型可以由
tmp推断,只指定输出类型):IEnumerable<string> test = tmp.Select<string>(x => ToString(x));显式转换Lambda的返回值:
给ToString(x)的结果加一个显式的string转换,强制编译器识别返回类型:IEnumerable<string> test = tmp.Select(x => (string)ToString(x));
内容的提问来源于stack exchange,提问作者Filip Stankovski

