mathieu
mathieu

Reputation: 31202

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 second method.

Upvotes: 14

Views: 4387

Answers (4)

Jon
Jon

Reputation: 437376

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.

Upvotes: 12

Andras Zoltan
Andras Zoltan

Reputation: 42343

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.

Upvotes: 3

Mahesh Velaga
Mahesh Velaga

Reputation: 21991

Try return t.Select(x => ToStr(x)) as IEnumerable<string>

Upvotes: 1

Darin Dimitrov
Darin Dimitrov

Reputation: 1038820

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));
}

Upvotes: 2

Related Questions