var、dynamic和linq组合时出现的奇怪行为变化

5

在下面这段代码的原始版本中,我(懒惰地)使用了var,结果在代码的完全不同部分出现了奇怪的运行时异常。将“var”更改为“int”可以解决运行时异常,但我还是不能完全理解为什么会出现这种情况。我将代码简化为以下示例:

public class Program
{
    private static List<string> Test(string i) { return new List<string> {i}; }
    private static dynamic GetD() { return 1; }

    public static void Main()
    {
        int value1 = GetD();   // <-- int
        var result1 = Test("Value " + value1);
        // No problem, prints "Value 1", First() on List<string> works ok.
        Console.WriteLine(result1.First());

        var value2 = GetD();   // <-- var
        var result2 = Test("Value " + value2);
        // The below line gives RuntimeBinderException 
        // 'System.Collections.Generic.List<string>' does not contain a 
        // definition for 'First'
        Console.WriteLine(result2.First());
    }
}

我看到"var"的类型是动态的而不是int,但为什么这个类型会传播到并影响调用Test()的返回值的行为呢?
编辑:也许我应该澄清我的问题;我可以看到dynamic传播到result2,但我不明白为什么当IDE明确指示调用List<string> Test(string)方法时,它仍然将返回值推断为动态。难道这是IDE比编译器更聪明的情况吗?

你的IDE明确地给出了所调用的方法吗?而我的没有(Visual Studio 2010 给了一个动态表达式)。你使用的是哪个IDE? - Cédric Bignon
@CédricBignon 在使用ReSharper的Visual Studio 2012中,点击调用并使用“转到定义”将跳转到该方法。此外,如果我将调用更改为“Test2”,它将标记调用为错误,并告诉我没有这样的方法可供调用。 - Joachim Isaksson
你的IDE并不是那么聪明。尝试添加新方法:private static List<int> Test(int i) { return new List<int> { i }; } 它将为您提供两种可能性。 - Cédric Bignon
3个回答

2

您的代码是这样编译的:

public static void Main()
{
    int value1 = GetD();   // <-- int
    List<string> result1 = Test("Value " + value1);
    // No problem, prints "Value 1", First() on List<string> works ok.
    Console.WriteLine(result1.First());

    dynamic value2 = GetD();   // <-- var
    dynamic result2 = Test("Value " + value2);
    // The below line gives RuntimeBinderException 
    // 'System.Collections.Generic.List<string>' does not contain a 
    // definition for 'First'
    Console.WriteLine(result2.First());
}

result2是一个动态对象,因此不支持在其上使用扩展方法(作为扩展方法使用)。

但是,你可以这样做:

Console.WriteLine(Enumerable.First(result2));

更新

您的集成开发环境(IDE)并不是那么聪明。尝试添加新的方法:

private static List<int> Test(int i) { return new List<int> { i }; }

它将向您提供两种可能性。

更新2

C#规范的第7.6.5段:

如果满足以下至少一项条件,则调用表达式是动态绑定的(§7.2.2):

  • 主表达式具有运行时类型dynamic。
  • 可选参数列表中的至少一个参数具有编译时类型dynamic,并且主表达式没有委托类型。

var result2 = Test("Value " + value2); 将被编译为 List<string> result2 = Test("Value " + value2); 而不是 dynamic result2 = Test("Value " + value2); - Hamlet Hakobyan
是的,没错,但编译器仍然“更喜欢”将其视为动态表达式。 - Cédric Bignon
好的,我理解了,但为什么?有规范的链接吗? - Hamlet Hakobyan
@CédricBignon,你的意思是说编译器更喜欢使用dynamic,即使它知道调用的方法和类型,只是为了不改变行为,以防以后添加其他重载?这有点合理,但对于通常在编译时尽可能推断出最具体类型信息的var来说,也有些令人困惑。我想我需要深入研究一下规范,因为显然dynamic并不直观 :) - Joachim Isaksson
我已经更新了我的帖子,并附上了C#规范的参考。 - Cédric Bignon
显示剩余2条评论

2
问题在于First是一个扩展方法而不是实例方法,运行时绑定器在动态区分扩展方法和实例方法方面存在困难。
您可以在此处阅读更多信息:
扩展方法和动态对象:Extension method and dynamic object

0

你可以看到下面的图片明显显示了可能存在的问题。

GetType() Value

result2的GetType()显示它是动态对象。

此外,dynamic关键字不支持扩展方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接