一尘不染

为什么即使只有一种可能的返回类型,方法调用表达式也具有动态类型?

c#

受到这个问题的启发。

简短版:M(dynamic arg)如果只有一个重载M或所有重载M具有相同的返回类型,为什么编译器无法弄清楚的编译时类型?

根据规范,第7.6.5节:

如果下列条件之一成立,则调用绑定是动态绑定的(第7.2.2节):

  • 主表达式具有动态的编译时类型。

  • 可选参数列表的至少一个参数的编译时类型为dynamic,而主表达式没有委托类型。

有道理的是

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

编译器无法找出

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

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

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

我想了解为什么规范不允许编译器在编译时静态键入这些表达式。


阅读 209

收藏
2020-05-19

共1个答案

一尘不染

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


M(dynamic_expression)如果只有M的一个重载,或者M的所有重载都具有相同的返回类型,为什么编译器为什么不能找出的编译类型类型?

编译器 可以 找出编译时的类型。编译时类型是 dynamic ,编译器成功地指出了这一点。

我认为您打算提出的问题是:

为什么编译时类型M(dynamic_expression)总是动态的,即使在极少数情况下,也就是对方法M进行完全不必要的动态调用时,无论参数类型如何,该方法都会始终被选择?

当您用这样的方式表达问题时,它就会回答自己。:-)

原因一:

您设想的情况很少见;为了使编译器能够进行您描述的那种推理,必须知道足够的信息,以便编译器可以对表达式进行几乎完整的静态类型分析。但是,如果您处于这种情况下,那么为什么首先要使用动态?简单地说:

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

显然,如果M只有一个重载,那么它甚至更容易: 将对象转换为所需的类型 。如果它在运行时由于强制转换失败而失败,那么动态也将失败!

在这种情况下,根本就 不需要 动态处理,因此为什么我们要在编译器中进行大量昂贵且困难的类型推断工作,以实现我们不希望动态处理使用的情况?

原因二:

假设我们确实说过,如果静态地知道方法组包含一个方法,则重载解析具有非常特殊的规则。大。现在,我们刚刚在语言中添加了一种新的易碎性。现在
添加新的重载将调用的返回类型更改为完全不同的类型 -这种类型不仅会导致动态语义,而且还会导致框值类型。但是,等等,情况变得更糟!

// 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 = d.M(dyn);

假设我们根据您的逻辑要求实现您的功能,并推断q为int。现在,Foo公司添加:

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

突然,当Baz公司重新编译其代码时,突然q的类型就悄悄地变成了动态,因为我们在编译时不知道dyn不是字符串。在静态分析中,这是一个 奇怪
而出乎意料的变化!为什么第三方 要在基类中 添加新方法
导致局部变量的类型在另一家公司(甚至不直接使用B的公司)编写的完全不同的类中以完全不同的方法更改,但仅通过D?

这是脆性基类问题的一种新形式,我们力求最大程度地减少C#中的脆性基类问题。

或者,如果相反,Foo公司说:

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

现在,按照您的逻辑,

var q = d.M(dyn);

当上面的代码在从D继承的类型 之外 时,给q类型int

var q = this.M(dyn);

使动态Q的类型时, 里面 一个类型继承d!作为开发人员,我会感到非常惊讶。

原因三:

C#已经太聪明了。我们的目标不是构建一个逻辑引擎,该逻辑引擎可以在给定特定程序的情况下针对所有可能的值计算出所有可能的类型限制。我们更喜欢具有通用的,易于理解的,易于理解的规则,这些规则可以轻松地写下并在没有错误的情况下实现。规范已经八百页长,编写无错误的编译器非常困难。让我们不要再变得困难了。更不用说
测试 所有那些疯狂案例的开销了。

原因四:

此外,该语言为您提供了许多利用静态类型分析器的机会。如果使用动态,则 专门要求该分析器将其操作推迟到runtime之前
。使用“在编译时停止进行静态类型分析”功能会导致静态类型分析在编译时无法很好地工作也就不足为奇了。

2020-05-19