考虑以下测试类(没有意义,但只是为了说明目的):
public class Test
{
public IEnumerable<string> ToEnumerableStrsWontCompile(IEnumerable<dynamic> t)
{
return t.Select(x => ToStr(x));
}
public IEnumerable<string> ToEnumerableStrsWillCompile(IEnumerable<dynamic> t)
{
var res = new List<string>();
foreach (var d in t)
{
res.Add(ToStr(d));
}
return res;
}
public string ToStr(dynamic d)
{
return new string(d.GetType());
}
}
为什么在t.Select(x => ToStr(x))
上编译时没有出现以下错误?
Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<dynamic>'
to 'System.Collections.Generic.IEnumerable<string>'. An explicit conversion
exists (are you missing a cast?)
第二个方法没有错误
我认为这里发生的事情是,由于表达式ToStr(x)
涉及dynamic
变量,整个表达式的结果类型也是dynamic
;这就是为什么编译器认为IEnumerable<dynamic>
应该是IEnumerable<string>
。
public IEnumerable<string> ToEnumerableStrsWontCompile(IEnumerable<dynamic> t)
{
return t.Select(x => ToStr(x));
}
有两种方法可以解决这个问题。
使用显式强制转换:
public IEnumerable<string> ToEnumerableStrsWontCompile(IEnumerable<dynamic> t)
{
return t.Select(x => (string)ToStr(x));
}
这告诉编译器表达式的结果肯定是一个字符串,所以我们最终得到一个IEnumerable<string>
。
用方法组替换lambda:
public IEnumerable<string> ToEnumerableStrsWontCompile(IEnumerable<dynamic> t)
{
return t.Select(ToStr);
}
这样编译器就隐式地将方法组表达式转换为lambda。注意,由于表达式中没有提到dynamic
变量x
,因此可以立即推断其结果的类型为string
,因为只需要考虑一个方法,其返回类型为string
。
似乎c#编译器正在确定第一个方法x => ToStr(x)
中的lambda的类型为Func<dynamic, dynamic>
,因此声明返回的IEnumerable
的类型为IEnumerable<dynamic>
。x => (string)ToStr(x)
的一个小改动似乎解决了这个问题。
这很可能是因为类型推断规则——因为如果你把这行改成这样:
return t.Select<dynamic, string>(x => ToStr(x));
编译没有错误。
所讨论的特定类型推断规则,然而,我不太确定-但是如果您使用以下代码:
public void foo(dynamic d)
{
var f = this.ToStr(d);
string s = f;
}
然后将鼠标悬停在编辑器中的'f'上,您将看到智能感知将表达式的类型报告为'动态f'。这是因为this.ToStr(d)
是一个动态表达式,无论方法本身及其返回类型在编译时是否已知。
编译器然后很高兴分配string s = f;
,因为它能够静态分析f
可能是什么类型,因为最终ToStr
总是返回一个字符串。
这就是为什么第一个方法需要强制转换或显式类型参数-因为编译器使ToStr
成为dynamic
类型;因为它至少包含一个动态表达式
试试:
public IEnumerable<string> ToEnumerableStrsWontCompile(IEnumerable<dynamic> t)
{
return t.Select(ToStr);
}
另一种可能是显式指定泛型参数:
public IEnumerable<string> ToEnumerableStrsWontCompile(IEnumerable<dynamic> t)
{
return t.Select<dynamic, string>(x => ToStr(x));
}
试试return t.Select(x => ToStr(x)) as IEnumerable<string>