假设我有这样的方法:
static string ToString(dynamic d)
{
return (string)d.ToString();
}
例如我有var tmp = new List<dynamic> { 1, "2", 345 };
为什么
IEnumerable<string> test = tmp.Select(ToString);
编译好,但
IEnumerable<string> test = tmp.Select(x => ToString(x));
没有按&#39;?吨
错误:
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 2015和VS 2017对所有&gt; = 4.5.0框架
都适用答案 0 :(得分:3)
您遇到了dynamic contagion。 dynamic
可以是任何东西,因此任何方法或属性都可以返回任何内容。您传递给它的任何方法都可以是任何方法,因为当您传递dynamic
作为参数时,会在运行时发生重载解析(如果您讨厌那些必须维护代码的人,则可以利用此方法)。
如果您将鼠标悬停在Select
上方,它的类型为Select<dynamic, string>
,则返回IEnumerable<String>
。由于ToString(dynamic d)
在string
中有return
显式转换,并返回类型string
,因此编译器可以确保ToString
确实返回一个字符串。
IEnumerable<string> test = tmp.Select(ToString);
顺便说一下,如果我们对ToString()进行这些更改,上面的代码仍会编译,而lambda版本仍然不会:
static string ToString(object d)
{
// Remove the cast to string
return d.ToString();
}
当我们将tmp
创建为List<dynamic>
时,就会引入传染病。
在无法编译的lambda版本中,将鼠标悬停显示我们实际上正在调用Select<dynamic, dynamic>
并返回IEnumerable<dynamic>
。
当您传递dynamic
时,过载分辨率在运行时完成。编译器无法保证在编译时实际调用什么方法或返回什么方法。 Intellisense认为ToString
是你的静态方法,但编译器并不相信它是真的。
IEnumerable<string> test2 = tmp.Select(x => ToString(x));
这个编译,因为我们有一个转换,其中重载决议不会影响它。
IEnumerable<string> test3 = tmp.Select(x => (string)ToString(x));
Jonathon Chase在评论中注意到我们通过将类型参数显式传递给Select
来编译它:
IEnumerable<string> test4 = tmp.Select<dynamic, string>(x => ToString(x));
在我看来,重要的问题就是你的第一个案例编译的原因。我谨慎的猜测是因为你传递了对方法的引用而不是调用它,动态重载解析不会发生了。
将此视为占位符,直到有更深入理解的人对该问题感兴趣。
答案 1 :(得分:2)
我不会重复Ed关于“动态传染”的回答。但是会试着说明第一个案例成功编译的原因。
这里
IEnumerable<string> test = tmp.Select(ToString);
ToString
是一个方法组 - 含糖功能,允许您使用某种快捷方式将方法作为具有适当签名的委托传递。可以在此帖子What is a method group in c#中找到一些其他信息。
这实际上是通过在封面下创建新的委托实例来实现的。因此,您的代码非常等同于
Func<dynamic, string> toString = new Func<dynamic, string>(ToString);
IEnumerable<string> test = tmp.Select(toString);
正如您所看到的,我们在这里有一个明确的委托返回类型,并且它不是动态的,因此我们不应该担心动态传染。