Generic type inference not working with dynamic?

空扰寡人 提交于 2019-12-10 08:23:12

问题


I have recently been playing with Massive, a Micro-ORM which returns collections of IEnumerable<dynamic>.

I discovered an unexpected issue when I tried to query one of those collections using LINQ.

While the compiler seems to have no issues whatsoever to work out that string.Format returns a string even when one of the arguments passed to it is declared as dynamic...

dynamic dynamicString = "d"; // just using a string here for simplicity, same problem occurs with any other type
string explicitString = string.Format("string is {0}", dynamicString); // works without issues

...it doesn't appear to be able to infer that fact in the following scenario:

IEnumerable<string> strings = new[] { "a", "b", "c" };
IEnumerable<dynamic> dynamics = strings;

IEnumerable<string> output = dynamics.Select(d => string.Format("string is {0}", d)); // compiler error on this line

The compiler complains:

"Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<dynamic>' to 'System.Collections.Generic.IEnumerable<string>'. An explicit conversion exists (are you missing a cast?)"

Since the compiler should be able to infer that my lambda expression returns a string, I would have expected it to also infer that the TResult of the Select should be of type string (and not dynamic).

It was easy to fix by specifying the TSource and TResult explicitly like this:

IEnumerable<string> output2 = dynamics.Select<dynamic, string>(d => string.Format("string is {0}", d)); // works !!!

Or I could assign the result to an IEnumerable<dynamic>...

IEnumerable<dynamic> output3 = dynamics.Select(d => string.Format("string is {0}", d)); // also works

I have also confirmed that this problem does not occur when I replace my IEnumerable<dynamic> with an IEnumerable<object>:

IEnumerable<object> objects = strings;
IEnumerable<string> output4 = objects.Select(o => string.Format("string is {0}", o)); // works

And interestingly even the following works:

IEnumerable<string> output5 = dynamics.Select(d => string.Format("string is {0}", (object)d)); // works
IEnumerable<string> output6 = dynamics.Select(d => string.Format("string is {0}", (string)d)); // works

Can anybody explain what's going on here? Is this a know limitation of the C# compiler or have I found yet another bug?


回答1:


You need:

IEnumerable<string> output = dynamics.Select(d => (string)string.Format(
       "string is {0}", d));

It can't infer the return type is string, because the dynamic means it has to assume the return is dynamic, in case there is a more suitable overload of string.Format for the specific type supplied (with a different return type). Even if we know otherwise, the spec for dynamic will disagree with us ;p By adding an explicit cast back to string we make the return type clear.

Personally, I see no use for dynamic here; you might as well use object, then it isn't an issue in the first place:

IEnumerable<string> strings = new[] { "a", "b", "c" };
IEnumerable<object> dynamics = strings;

IEnumerable<string> output = dynamics.Select(d => string.Format(
      "string is {0}", d));

(or indeed, leave as IEnumerable<string>) I assume you have some other reason to use dynamic that isn't visible in this example.




回答2:


I think the problem is unrelated to dynamics. I often have a 'user expectation failure' happen when hoping that .Select<> will infer the generic type parameters1.

You could solve it like so:

 Func<dynamic, string> selector = d => string.Format("string is {0}", d);
 IEnumerable<string> output = dynamics.Select(selector);

1 I'll try to add an example of precisely such 'surprisingly ambiguous' cases when I have time later



来源:https://stackoverflow.com/questions/7951021/generic-type-inference-not-working-with-dynamic

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!