dynamic, linq and Select()
Considering the following (pointless, but it's for illustration purpose) test class :
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());
}
}
Why doesn't it compile with the following error, on 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?)
No error on the secon开发者_如何学God method.
I believe what happens here is that since the expression ToStr(x)
involves a dynamic
variable, the whole expression's result type is also dynamic
; that's why the compiler thinks that it has an IEnumerable<dynamic>
where it expects an IEnumerable<string>
.
public IEnumerable<string> ToEnumerableStrsWontCompile(IEnumerable<dynamic> t)
{
return t.Select(x => ToStr(x));
}
There are two ways you can fix this.
Use an explicit cast:
public IEnumerable<string> ToEnumerableStrsWontCompile(IEnumerable<dynamic> t)
{
return t.Select(x => (string)ToStr(x));
}
This tells the compiler that the result of the expression is definitely going to be a string, so we end up with an IEnumerable<string>
.
Replace the lambda with a method group:
public IEnumerable<string> ToEnumerableStrsWontCompile(IEnumerable<dynamic> t)
{
return t.Select(ToStr);
}
This way the compiler implicitly converts the method group expression to a lambda. Note that since there is no mention of the dynamic
variable x
in the expression, the type of its result can be immediately inferred to be string
because there is only one method to consider, and its return type is string
.
it would appear that the C# compiler is determining the type of the lambda in the first method x => ToStr(x)
as Func<dynamic, dynamic>
and therefore declaring the type of the IEnumerable
returned as IEnumerable<dynamic>
. A small change x => (string)ToStr(x)
seems to fix it.
This is most likely because of type inference rules - because if you change the line to this:
return t.Select<dynamic, string>(x => ToStr(x));
It compiles without error.
The specific type inference rule in question, however, I'm not too sure of - however if you take this code:
public void foo(dynamic d)
{
var f = this.ToStr(d);
string s = f;
}
And then hover over the 'f' in the editor you will see that intellisense reports the type of the expression as 'dynamic f'. This will be because the this.ToStr(d)
is a dynamic expression, regardless of whether the method itself, and its return type, is known at compile time.
The compiler then is happy to assign string s = f;
because it is able to statically analyse the type that f
is likely to be, because ultimately ToStr
always returns a string.
This is why ths first method requires a cast or explicit type parameters - because the compiler is making ToStr
be of dynamic
type; because it has at least one dynamic expression as part of it.
Try like this:
public IEnumerable<string> ToEnumerableStrsWontCompile(IEnumerable<dynamic> t)
{
return t.Select(ToStr);
}
Another possibility is to specify explicitly the generic arguments:
public IEnumerable<string> ToEnumerableStrsWontCompile(IEnumerable<dynamic> t)
{
return t.Select<dynamic, string>(x => ToStr(x));
}
Try return t.Select(x => ToStr(x)) as IEnumerable<string>
精彩评论