为什么方法调用expression式只有一种可能的返回types时才具有dynamictypes?

受这个问题的启发。

简短版本:如果M只有一个重载或M所有重载具有相同的返回types,为什么编译器不能找出M(dynamic arg)的编译时间types?

根据规范,§7.6.5:

如果至less存在以下情况之一,则调用expression式将被dynamic绑定(第7.2.2节):

  • 主expression式具有编译时dynamictypes。

  • 可选参数列表中至less有一个参数具有编译时typesdynamic,并且主expression式没有委托types。

这是有道理的

 class Foo { public int M(string s) { return 0; } public string M(int s) { return String.Empty; } } 

编译器无法弄清楚编译时的types

 dynamic d = // dynamic var x = new Foo().M(d); 

因为直到运行时才会知道调用了哪个M重载。

但是,如果M只有一个重载或M所有重载返回相同types,为什么编译器不能找出编译时types?

我正在理解为什么规范不允许编译器在编译时静态地input这些expression式。

更新:这个问题是我的博客在2012年10月22日的主题 。 感谢您的好问题!


为什么编译器无法找出M(dynamic_expression)的编译types,如果只有一个M的重载或M的所有重载具有相同的返回types?

编译器可以计算出编译时的types; 编译时types是dynamic的 ,编译器能够成功地计算出来。

我想你要问的问题是:

为什么M(dynamic_expression)dynamicM(dynamic_expression)的编译时间types总是dynamic的,即使在极less数情况下,即使对于方法M进行完全不必要的dynamic调用,将始终被选中而不pipe参数types如何?

当你这样说的问题,它有点自己回答。 🙂

理由一:

你设想的情况是罕见的; 为了使编译器能够做出你所描述的推理types,必须知道足够的信息,以便编译器可以对expression式进行几乎完整的静态types分析。 但是,如果你是在这种情况下,那么你为什么首先使用dynamic? 你会做得更好,简单地说:

 object d = whatever; Foo foo = new Foo(); int x = (d is string) ? foo.M((string)d) : foo((int)d); 

显然,如果M只有一个重载,那么它更容易: 将对象转换为所需的types 。 如果它在运行时失败,因为投它不好,dynamic也会失败!

在这种情况下,首先根本就不需要dynamic的,那么为什么我们要在编译器中做很多昂贵而又困难的types推断工作,以使我们不希望你首先使用dynamic的场景?

原因二:

假设我们确实说过重载决议有非常特殊的规则,如果方法组是静态地知道包含一个方法。 大。 现在我们刚刚为这个语言增加了一种新的脆弱性。 现在, 添加一个新的重载会将调用的返回types更改为完全不同的types – 这种types不仅会引起dynamic语义,而且还会引起框值types。 但是等等,情况变得更糟!

 // Foo corporation: class B { } // Bar corporation: class D : B { public int M(int x) { return x; } } // Baz corporation: dynamic dyn = whatever; D d = new D(); var q = dM(dyn); 

假设我们通过你的逻辑来实现你的特征,并且推断q是int。 现在Foo公司补充说:

 class B { public string M(string x) { return x; } } 

突然间,当Baz公司重新编译它们的代码时,突然间,q的types悄然变成dynamic的,因为我们在编译时不知道dyn不是一个string。 这是静态分析中一个奇怪而意想不到的变化! 为什么第三方在基类中增加一个新方法会导致局部variables的types在一个完全不同的类中完全不同的方法中改变,这个方法是写在另一个公司,甚至不直接使用B的公司,但只能通过D?

这是脆弱基类问题的新forms,我们试图最小化C#中的脆弱基类问题。

或者,如果Foo公司说:

 class B { protected string M(string x) { return x; } } 

现在,按照你的逻辑,

 var q = dM(dyn); 

当上面的代码在从Dinheritance的types之外时给出inttypes,但是

 var q = this.M(dyn); 

在从Dinheritance的types中给出q的types为dynamic的。 作为一名开发人员,我会觉得很奇怪。

原因三:

C#中有太多的巧妙了。 我们的目标不是build立一个逻辑引擎,在给定特定程序的情况下,对所有可能的值进行所有可能的types限制。 我们更喜欢有一般的,可以理解的,易于理解的规则,这些规则可以很容易地写下来,而且没有bug。 这个规范已经有800页长了,编写一个无bug的编译器是非常困难的。 我们不要让它更难。 更不用说testing所有这些疯狂的案例了。

原因四:

此外:语言为您提供了许多机会来利用静态型分析仪。 如果您使用的是dynamic的,那么您特别要求分析器将其操作推迟到运行时间 。 使用“在编译时停止执行静态types分析”function导致静态types分析在编译时不能很好地工作,这不应该是一个惊喜。

dynamic特性的早期devise支持这样的事情。 编译器仍然会执行静态重载parsing,并且只在必要时才引入表示dynamic重载parsing的“幻像重载”。

  • 博客文章介绍幻影方法
  • 有关幻影方法的详细信息

正如你在第二篇文章中所看到的,这种方法引入了很多复杂性(第二篇文章讨论了如何修改types推断以使这个方法成功)。 我不惊讶的是,C#团队决定采用dynamic重载解决scheme时总是使用dynamic重载的简单思路。

但是,如果M只有一个重载或M的所有重载返回相同types,为什么编译器不能找出编译时types?

编译器可能会这样做,但是语言团队决定不这样做。

dynamic的全部目的是让所有使用dynamicexpression式的expression式都以“它们的分辨率被推迟到程序运行”(C#spec,4.2.3)来执行。 编译器显式地不执行dynamicexpression式的静态绑定(这将需要得到你想要的行为)。

如果只有一个绑定选项,则回退到静态绑定会强制编译器检查这个案例 – 哪个没有被添加。至于为什么语言团队不想这样做,我怀疑Eric Lippert的回答在这里适用:

我问“为什么不C#实现functionX? 每时每刻。 答案总是一样的,因为从来没有人devise,指定,实施,testing,logging和发运该function。

我认为能够静态确定dynamic方法parsing的唯一可能的返回types的情况非常狭窄,以至于如果C#编译器做到了这一点,将会更加混乱和不一致,而不是全面的行为。

即使在你的例子中,如果Foo是不同的DLL的一部分, Foo可能是一个新的版本在运行时从绑定redirect与其他M具有不同的返回types,然后编译器会猜测错误,因为运行时分辨率将返回一个不同的types。

如果Foo是一个IDynamicMetaObjectProvider,那么 d可能不匹配任何静态参数,因此它会回退它的dynamic行为,这可能会返回一个不同的types。