开发者

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>

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜