在解析动态对象时,C#是否为var选择了错误的类型?

我使用以下代码将一些Json转换为动态对象。 当我在动态类型的属性上使用DateTime.Parse时,我希望var猜测它的类型是DateTime …而是保持动态。 这不可能是对的,可以吗?

完整的例子如下。

var settings = new JavaScriptSerializer().Deserialize(json); var startDate = DateTime.Parse(settings.startDate); var endDate = DateTime.Parse(settings.endDate); var userId = int.Parse(settings.userId); 

startDate,endDate和userId都是动态的,这意味着我不能在以后的Lambda表达式中使用它们。 显然,我可以用以下代码修复代码:

 DateTime startDate = DateTime.Parse(settings.startDate); DateTime endDate = DateTime.Parse(settings.endDate); int userId = int.Parse(settings.userId); 

..但似乎编译器正在做出“糟糕的猜测”。 任何人都可以向我解释这个吗?

谢谢

..但似乎编译器正在做出“糟糕的猜测”。 任何人都可以向我解释这个吗?

当您使用dynamic ,整个表达式在编译时被视为动态表达式 ,这会导致编译器将所有内容视为动态并获取运行时绑定。

这在C#语言规范的7.2中有解释:

当不涉及动态表达式时,C#默认为静态绑定,这意味着在选择过程中使用组成表达式的编译时类型。 但是,当上面列出的操作中的一个组成表达式是动态表达式时,操作将被动态绑定。

这基本上意味着大多数操作(类型在规范的7.2节中列出)具有任何声明为dynamic元素将被评估为dynamic ,结果将是dynamic

在您的情况下,此声明:

 var settings = new JavaScriptSerializer().Deserialize(json); 

使用动态,因此,它被视为动态表达式。 由于“方法调用”是受绑定(7.2)的C#操作之一,因此编译器将其视为动态绑定,这会导致其评估为:

 dynamic settings = new JavaScriptSerializer().Deserialize(json); 

反过来,这会导致DateTime.Parse表达式被动态绑定,从而使它们返回dynamic

当你执行DateTime startDate = DateTime.Parse(settings.startDate);时,你的“修复”有效DateTime startDate = DateTime.Parse(settings.startDate); 因为这会强制将DateTime.Parse方法的结果隐式动态转换(在规范的第6.1.8节中描述)到DateTime:

从dynamic类型的表达式到任何类型T都存在隐式动态转换。转换是动态绑定的(第7.2.2节),这意味着将在运行时从表达式的运行时类型中寻找隐式转换如果没有找到转换,则抛出运行时exception。

在这种情况下,转换是有效的,因此您可以有效地将所有内容切换回静态绑定。

我不认为这特别令人惊讶。

DateTime.Parse()将评估为动态。

DateTime startDate = 执行从动态到DateTime的运行时分配。

你刚刚结合了这两个。

编译器没有猜测DateTime.Parse()的类型是DateTime.Parse()以外的任何东西,但是它很聪明地意识到如果你将这个值赋值给DateTime然后假设它成功了你就是留下了DateTime。

这符合规范。 见§7.6.5:

如果至少满足下列条件之一,则动态绑定调用表达式(第7.2.2节):

primary-expression具有编译时类型dynamic

•可选参数列表的至少一个参数具有编译时类型dynamic ,而primary-expression没有委托类型。

考虑这种情况:

 class Foo { public int M(string s) { return 0; } public string M(int s) { return String.Empty; } } Foo foo = new Foo(); dynamic d = // something dynamic var m = foo.M(d); 

什么应该是m的编译时类型? 编译器无法分辨,因为直到运行时才会知道调用Foo.M重载。 因此,它说m是动态的。

现在,您可以说它应该能够找出DateTime.Parse只有一个重载,即使它没有,但它的所有重载都具有相同的返回类型,在这种情况下它应该能够找出编译时类型。 这将是一个公平的观点,也许是最好的Eric Lippert。 我问了一个单独的问题来深入了解这个: 为什么即使只有一种可能的返回类型,方法调用表达式也具有动态类型? 。

这里有两个不同的概念

  1. dynamic :在运行时解析的任何类型
  2. var :这是在编译时执行的隐式静态类型

所以,如果你这样做

 var settings = new JavaScriptSerializer().Deserialize(json); var startDate = DateTime.Parse(settings.startDate); 

在编译时,它被解析为动态类型,并在运行时将其解析为具体类型。 编译器检查正确的部分new JavaScriptSerializer().Deserialize(json); ,返回动态。 在编译时,这指示编译器删除所有类型安全检查并将其保持到运行时。

这段代码

 var settings = new JavaScriptSerializer().Deserialize(json); DateTime startDate = DateTime.Parse(settings.startDate); 

显式地说动态对象是具体类型,因此编译器能够推断出类型及其所有方法,并且能够在编译时执行静态类型检查。