为什么C#3.0对象初始值设定项构造函数的括号是可选的?

看起来,C#3.0对象初始化程序语法允许在存在无参数构造函数的情况下,排除构造函数中的开放/closures括号对。 例:

var x = new XTypeName { PropA = value, PropB = value }; 

而不是:

 var x = new XTypeName() { PropA = value, PropB = value }; 

我很好奇为什么构造函数打开/closures括号对在XTypeName后是可选的?

这个问题是我的博客在2010年9月20日的主题 。 乔希和乍得的答案(“他们不加价值,为什么要求他们?”和“消除冗余”)基本上是正确的。 为了充实这一点:

允许您将参数列表作为对象初始值设定项的“较大特征”的一部分的特征符合我们的“含糖”特征的栏。 我们考虑的一些观点:

  • devise和规格成本低
  • 我们将会广泛地改变处理对象创build的parsing器代码, 使参数列表可选的额外开发成本与较大特征的成本相比并不大
  • 与较大特征的成本相比,testing负担相对较小
  • 文件负担比较小…
  • 预计维护负担很小; 我不记得自发货以来,在这个function中报告的任何错误。
  • 该function对该领域的未来function不构成任何明显的风险。 (我们想要做的最后一件事是制作一个便宜,简单的function,这使得未来实现更引人注目的function变得更加困难。)
  • 该特征不会给语言的词汇,语法或语义分析添加新的歧义。 它不会对您在键入时由IDE的“智能感知”引擎执行的那种“部分程序”分析造成任何问题。 等等。
  • 该特性为较大的对象初始化特性创造了一个共同的“甜蜜点” 通常如果您使用的是对象初始值设定项,则正是因为对象的构造函数不允许您设置所需的属性。 这样的物体在一开始就只是简单地成为没有参数的“物品包”是很常见的。

那么为什么你还没有在没有对象初始值设定项的对象创buildexpression式的默认构造函数调用中使用空括号?

再看看上面的标准列表。 其中之一就是这种改变不会在程序的词汇,语法或语义分析中引入任何新的歧义。 您提出的更改确实引入了语义分析模糊性:

 class P { class B { public class M { } } class C : B { new public void M(){} } static void Main() { new C().M(); // 1 new CM(); // 2 } } 

第1行创build一个新的C,调用默认的构造函数,然后在新对象上调用实例方法M. 第2行创build一个BM的新实例并调用其默认构造函数。 如果第1行的括号是可选的,那么第2行是不明确的。 那么我们就必须提出一个解决模棱两可的规则。 我们不能把它作为一个错误,因为那将是一个突破的变化,将现有的合法的C#程序变成一个破碎的程序。

因此,这个规则必须非常复杂:从本质上来说,括号只是在没有引入歧义的情况下是可选的。 我们必须分析所有可能导致歧义的案例,然后在编译器中编写代码来检测它们。

有鉴于此,请回头看看我提到的所有费用。 现在有多less人变大了? 复杂的规则有大量的devise,规格,开发,testing和文档成本。 复杂的规则很可能会导致将来出现与function的意外交互的问题。

一切为了什么? 一个小小的客户利益,不增加语言的新代表权力,但增加了疯狂的angular落案件,只是等待一个可怜的毫无防备的灵魂碰壁而“喊”。 像这样的function立即被切断并放在“从不做这个”列表。

你是如何确定这种特殊的含糊之处的?

那个立刻就清楚了; 我非常熟悉C#中的规则,以确定何时应使用虚线名称。

当考虑一个新的function,你如何确定是否造成任何歧义? 手工通过formscertificate,通过机器分析,是什么?

三个全部。 大部分我们只是看上面的规格和面条,就像我上面所做的那样。 例如,假设我们想为C#添加一个名为“frob”的新的前缀运算符:

 x = frob 123 + 456; 

(更新: frob当然是await ;这里的分析本质上是devise团队在添加await时经历的分析。)

“frob”在这里就像“新”或“++” – 它在某种expression之前。 我们将找出所需的优先级和关联性等等,然后开始提问“如果程序已经具有types,字段,属性,事件,方法,常量或本地名为frob? 这会立即导致如下情况:

 frob x = 10; 

这是否意味着“对x = 10的结果做frob操作,或者创build一个types为frob的variables,并将其赋值为10? (或者,如果frobbing产生一个variables,那么它可能被赋值为10,所以frob x *x = 10;parsing并且如果xint*则是合法的)。

 G(frob + x) 

这是否意味着“在x上的一元加运算符的结果”或“在f上添加expression式”?

等等。 为了解决这些歧义,我们可能会引入启发式。 当你说“var x = 10”时 这是模糊的; 它可能意味着“推断x的types”或可能意味着“x是vartypes的”。 所以我们有一个启发式:我们首先尝试查找一个名为var的types,并且只有当一个不存在时,我们才能推断出x的types。

或者,我们可能会改变语法,使其不含糊不清。 当他们deviseC#2.0时,他们有这个问题:

 yield(x); 

这是否意味着“在迭代器中产生x”或“用参数x调用yield方法?” 通过改变它

 yield return(x); 

现在是明确的。

对于对象初始值设定项中的可选对象,可以直接推断是否引入了歧义,因为允许引入以{开头的内容很less的情况的数量 。 基本上只是各种语句上下文,语句lambdas,数组初始化和这是关于它。 通过所有的情况来推理是很容易的,并且表明没有歧义。 确保IDE保持高效有点困难,但可以做到没有太多的麻烦。

这种摆弄规范通常是足够的。 如果这是一个特别棘手的function,那么我们拔出较重的工具。 例如,在deviseLINQ时,其中一位编译人员和一位在parsing器理论方面具有背景知识的IDE人员自己构build了一个parsing器生成器,可以分析寻找歧义的语法,然后将提出的C#语法提供给查询parsing; 这样做会发现很多情况下查询模糊不清。

或者,当我们在C#3.0中对lambda进行高级types推断时,我们提出了我们的build议,然后将它们发送到剑桥的微软研究院,那里的语言团队已经足够处理了一个正式的证据,即types推断提议是理论上合理。

C#今天有什么含糊之处吗?

当然。

 G(F<A, B>(0)) 

在C#1中清楚这意味着什么。 这是一样的:

 G( (F<A), (B>0) ) 

也就是说,它用两个bools参数来调用G。 在C#2中,这可能意味着它在C#1中意味着什么,但它也可能意味着“将0传递给通用方法F,该types方法F接受types参数A和B,然后将F的结果传递给G”。 我们给parsing器添加了一个复杂的启发式,它决定了你可能意味着哪两种情况。

同样,即使在C#1.0中,强制转换也是模糊的:

 G((T)-x) 

是“cast -x to T”还是“从T中减去x”? 再次,我们有一个启发式的猜测。

因为这是如何指定的语言。 他们没有增加任何价值,为什么要包括他们?

它也非常类似于显式types的数组

 var a = new[] { 1, 10, 100, 1000 }; // int[] var b = new[] { 1, 1.5, 2, 2.5 }; // double[] var c = new[] { "hello", null, "world" }; // string[] var d = new[] { 1, "one", 2, "two" }; // Error 

参考: http : //msdn.microsoft.com/en-us/library/ms364047%28VS.80%29.aspx

这样做是为了简化对象的构造。 语言devise者没有(据我所知)具体地说,为什么他们认为这是有用的,虽然它在C#版本3.0规范页面明确提到:

对象创buildexpression式可以省略构造函数参数列表并包含圆括号,只要它包含对象或集合初始值设定项即可。 省略构造函数参数列表和括号括起来相当于指定一个空的参数列表。

我想他们觉得在这种情况下,为了显示开发人员的意图,括号是没有必要的,因为对象初始值设定程序显示了构build和设置对象属性的意图。

在第一个示例中,编译器推断您正在调用默认构造函数(C#3.0语言规范指出,如果没有提供括号,则调用默认构造函数)。

第二,你明确地调用默认的构造函数。

您还可以使用该语法来设置属性,同时将值显式传递给构造函数。 如果您有以下类定义:

 public class SomeTest { public string Value { get; private set; } public string AnotherValue { get; set; } public string YetAnotherValue { get; set;} public SomeTest() { } public SomeTest(string value) { Value = value; } } 

这三个陈述都是有效的:

 var obj = new SomeTest { AnotherValue = "Hello", YetAnotherValue = "World" }; var obj = new SomeTest() { AnotherValue = "Hello", YetAnotherValue = "World"}; var obj = new SomeTest("Hello") { AnotherValue = "World", YetAnotherValue = "!"}; 

我不是Eric Lippert,所以我不能肯定地说,但我会认为这是因为编译器不需要空括号来推断初始化结构。 因此它变成了冗余信息,而不是必需的。