dynamic, linq and Select()

本文关键字:Select and linq dynamic | 更新日期: 2023-09-27 17:54:29

考虑以下测试类(没有意义,但只是为了说明目的):

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?)

第二个方法没有错误

dynamic, linq and Select()

我认为这里发生的事情是,由于表达式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>