This question was the subject of my blog on September 20th 2010。 Josh 和 Chad 的回答(“它们没有增加任何价值,所以为什么需要它们?”和“消除冗余”)基本上是正确的。再充实一点:
允许您将参数列表作为对象初始值设定项的“更大功能”的一部分省略的功能符合我们对“糖分”功能的要求。我们考虑的几点:
- 设计和规范成本低
- 无论如何,我们将广泛更改处理对象创建的解析器代码;与较大功能的成本相比,使参数列表可选的额外开发成本并不大
- 与较大功能的成本相比,测试负担相对较小
- 与此相比,文档负担相对较小...
- 预计维护负担很小;我不记得自该功能发布以来的几年中报告的任何错误。
- 该功能不会对该领域的未来功能构成任何立即明显的风险。 (我们想做的最后一件事是现在制作一个便宜、简单的功能,这使得将来实现更具吸引力的功能变得更加困难。)
- 该功能不会为语言的词汇、语法或语义分析增加新的歧义。它对于在您键入时由 IDE 的“IntelliSense”引擎执行的那种“部分程序”分析没有任何问题。等等。
- 该功能达到了较大对象初始化功能的常见“最佳位置”;通常,如果您使用对象初始化器,正是因为对象的构造函数不允许允许您设置所需的属性。此类对象通常只是最初在 ctor 中没有参数的“属性包”。
那你为什么不在没有有对象初始化器的对象创建表达式的默认构造函数调用中也使空括号可选?
再看看上面的标准列表。其中之一是该更改不会在程序的词汇、语法或语义分析中引入任何新的歧义。您提出的更改确实引入了语义分析歧义:
class P
{
class B
{
public class M { }
}
class C : B
{
new public void M(){}
}
static void Main()
{
new C().M(); // 1
new C.M(); // 2
}
}
第 1 行创建一个新的 C,调用默认构造函数,然后在新对象上调用实例方法 M。第 2 行创建 B.M 的新实例并调用其默认构造函数。 如果第 1 行的括号是可选的,那么第 2 行就会有歧义。 然后我们必须想出一个规则来解决歧义;我们无法将其设为错误,因为这将是一个破坏性更改,将现有的合法 C# 程序更改为损坏的程序。
因此,规则必须非常复杂:基本上,括号仅在不引入歧义的情况下是可选的。我们必须分析所有可能引入歧义的情况,然后在编译器中编写代码来检测它们。
鉴于此,回头看看我提到的所有费用。现在有多少变大了?复杂的规则具有巨大的设计、规范、开发、测试和文档成本。复杂的规则更有可能在未来与功能进行意外交互时出现问题。
为了什么?一个微小的客户利益,它没有为语言增加新的表现力,但确实增加了疯狂的角落案例,只是等待对遇到它的某个可怜的毫无戒心的灵魂大喊“抓住了”。像这样的功能会立即被删掉,并放在“从不这样做”的名单上。
您是如何确定这种特定的歧义的?
这一点立刻就清楚了;我非常熟悉 C# 中用于确定何时应使用带点名称的规则。
在考虑一项新功能时,您如何确定它是否会引起歧义?手工、形式证明、机器分析,什么?
这三个。大多数情况下,我们只看上面的规格和面条,就像我在上面所做的那样。例如,假设我们想在 C# 中添加一个名为“frob”的新前缀运算符:
x = frob 123 + 456;
(UPDATE:frob当然是await;这里的分析本质上是设计团队在添加await时经过的分析。)
“frob”在这里就像“new”或“++”——它出现在某种表达式之前。我们会计算出所需的优先级和关联性等等,然后开始问诸如“如果程序已经有一个类型、字段、属性、事件、方法、常量或本地称为 frob 怎么办?”这将立即导致以下情况:
frob x = 10;
这是否意味着“对 x = 10 的结果进行 frob 运算,或者创建一个名为 x 的 frob 类型的变量并将 10 分配给它?” (或者,如果 frobbing 产生一个变量,它可能是 10 到 frob x 的赋值。毕竟,如果 x 是 int*,*x = 10; 解析并且是合法的。)
G(frob + x)
这意味着“frob x 上一元加号运算符的结果”还是“将表达式 frob 添加到 x”?
等等。为了解决这些歧义,我们可能会引入启发式算法。当你说“var x = 10;”时这是模棱两可的;它可能意味着“推断 x 的类型”,也可能意味着“x 是 var 类型”。所以我们有一个启发式方法:我们首先尝试查找一个名为 var 的类型,只有当一个类型不存在时才推断 x 的类型。
或者,我们可能会更改语法以使其不模棱两可。他们在设计 C# 2.0 时遇到了这个问题:
yield(x);
这是否意味着“在迭代器中生成 x”或“使用参数 x 调用 yield 方法?”通过将其更改为
yield return(x);
现在是明确的。
在对象初始化器中的可选括号的情况下,很容易推断是否引入了歧义,因为允许引入以 { 开头的东西的情况非常少。基本上只是各种语句上下文、语句 lambdas、数组初始值设定项,仅此而已。很容易推理所有案例并表明没有歧义。确保 IDE 保持高效有些困难,但可以轻松完成。
这种对规范的摆弄通常就足够了。如果这是一个特别棘手的功能,那么我们会使用更重的工具。例如,在设计 LINQ 时,其中一位编译器人员和一位 IDE 人员都具有解析器理论背景,他们自己构建了一个解析器生成器,可以分析语法以查找歧义,然后将用于查询理解的建议 C# 语法输入其中;这样做会发现许多查询不明确的情况。
或者,当我们在 C# 3.0 中对 lambdas 进行高级类型推断时,我们编写了我们的建议,然后将它们发送到剑桥的 Microsoft 研究中心,那里的语言团队已经足够好,可以对类型进行正式证明推理提议在理论上是合理的。
今天的 C# 是否存在歧义?
当然。
G(F<A, B>(0))
在 C# 1 中,这意味着什么很清楚。同理:
G( (F<A), (B>0) )
也就是说,它使用两个布尔参数调用 G。在 C# 2 中,这可能意味着它在 C# 1 中的含义,但也可能意味着“将 0 传递给采用类型参数 A 和 B 的泛型方法 F,然后将 F 的结果传递给 G”。我们向解析器添加了一个复杂的启发式算法,它可以确定您可能指的是两种情况中的哪一种。
同样,即使在 C# 1.0 中,强制转换也是模棱两可的:
G((T)-x)
这是“将 -x 转换为 T”还是“从 T 中减去 x”?同样,我们有一个很好的猜测。