为什么C#3.0对象初始化器构造函数括号是可选的?

Jam*_*nne 111 c# syntax types language-design initializer

似乎C#3.0对象初始化程序语法允许在存在无参数构造函数时排除构造函数中的打开/关闭括号对.例:

var x = new XTypeName { PropA = value, PropB = value };
Run Code Online (Sandbox Code Playgroud)

相反:

var x = new XTypeName() { PropA = value, PropB = value };
Run Code Online (Sandbox Code Playgroud)

我很好奇为什么构造函数打开/关闭括号对后面是可选的XTypeName

Eri*_*ert 141

这个问题是我2010年9月20日博客的主题.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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

因此,规则必须非常复杂:基本上,括号仅在不引入歧义的情况下是可选的.我们必须分析引入歧义的所有可能情况,然后在编译器中编写代码来检测它们.

在这方面,请回过头来看看我提到的所有费用.现在有多少人变大了?复杂的规则具有较大的设计,规范,开发,测试和文档成本.复杂的规则更有可能导致未来与功能的意外交互出现问题.

全是为了什么?一个微小的客户利益,没有为语言增加新的代表性能力,但确实增加了疯狂的角落案件只是等着对一些可怜的毫无戒心的灵魂大喊"陷入困境".像这样的功能立即被削减,并把"永远不要这样"列表.

你是如何确定这种特殊模糊性的?

那一个很快就清楚了; 我非常熟悉C#中的规则,用于确定何时出现虚线名称.

在考虑新功能时,您如何确定它是否会导致任何歧义?手工,通过形式证明,通过机器分析,什么?

所有这三个.我们大多只看上面的规格和面条,正如我上面所做的那样.例如,假设我们想要向C#添加一个名为"frob"的新前缀运算符:

x = frob 123 + 456;
Run Code Online (Sandbox Code Playgroud)

(更新:frob当然await;此处的分析基本上是设计团队在添加时经历的分析await.)

这里的"frob"就像"new"或"++" - 它出现在某种表达之前.我们计算出所需的优先级和关联性等等,然后开始提出诸如"如果程序已经有类型,字段,属性,事件,方法,常量或本地名为frob?"的问题?这会立即导致以下情况:

frob x = 10;
Run Code Online (Sandbox Code Playgroud)

这是否意味着"对x = 10的结果执行frob操作,或者创建一个名为frob的frob类型的变量并为其分配10?" (或者,如果frobbing产生一个变量,它可能是10的赋值frob x.毕竟,*x = 10;解析并且是合法的,如果xint*.)

G(frob + x)
Run Code Online (Sandbox Code Playgroud)

这是否意味着"对x上的一元加运算符的结果"或"将表达式frob添加到x"?

等等.为了解决这些含糊之处,我们可能会介绍启发式方法.当你说"var x = 10;"时 这是模棱两可的; 它可能意味着"推断x的类型"或它可能意味着"x是var类型".所以我们有一个启发式:我们首先尝试查找一个名为var的类型,并且只有当一个不存在时,我们才推断出x的类型.

或者,我们可能会更改语法,使其不含糊.当他们设计C#2.0时他们遇到了这个问题:

yield(x);
Run Code Online (Sandbox Code Playgroud)

这是否意味着"在迭代器中产生x"或"使用参数x调用yield方法?" 通过将其更改为

yield return(x);
Run Code Online (Sandbox Code Playgroud)

它现在是明确的.

在对象初始化器中的可选parens的情况下,可以直接推断是否引入了歧义,因为允许引入以{开头的东西'的情况的数量非常小.基本上只是各种语句上下文,语句lambdas,数组初始化器以及它的相关内容.通过所有案例很容易推理,并表明没有歧义.确保IDE保持高效有点困难,但可以毫不费力地完成.

这种摆弄规格通常就足够了.如果这是一个特别棘手的功能,那么我们就会拿出更重的工具.例如,在设计LINQ时,其中一个编译人员和一个具有解析器理论背景的IDE人员自己构建了一个解析器生成器,可以分析查找歧义的语法,然后将提出的C#语法用于查询理解. ; 这样做发现了许多查询含糊不清的案例.

或者,当我们在C#3.0中对lambdas进行高级类型推断时,我们写了我们的建议,然后将它们发送到剑桥的微软研究院,那里的语言团队足够好,可以提出类型推断提案的正式证据.理论上听起来很合理.

C#今天有歧义吗?

当然.

G(F<A, B>(0))
Run Code Online (Sandbox Code Playgroud)

在C#1中,很清楚这意味着什么.它与以下相同:

G( (F<A), (B>0) )
Run Code Online (Sandbox Code Playgroud)

也就是说,它用两个bool的参数调用G. 在C#2中,这可能意味着它在C#1中的含义,但它也可能意味着"将0传递给采用类型参数A和B的泛型方法F,然后将F的结果传递给G".我们在解析器中添加了一个复杂的启发式算法,它决定了你可能意味着哪两种情况.

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

G((T)-x)
Run Code Online (Sandbox Code Playgroud)

是"cast -x to T"还是"从T中减去x"?同样,我们有一个很好的猜测启发式.

  • @Eric,你有没有可能在博客上发表关于这个"从不这样做"的名单?我很想看到其他不会成为C#语言的例子:) (8认同)
  • 我建议您避免发布帖子.对那些可能对这个问题有更多见解的人来说,这不会是公平的.更好的方法是发布问题,然后通过电子邮件发送一个链接,要求参与. (5认同)
  • 哦对不起,我忘记了...蝙蝠信号方法,虽然看起来有效,但优于(IMO)直接接触方式,因此人们不会以SO帖子的形式获得公众教育所需的公众曝光率.是可索引的,可搜索的,并且能够容易地被引用.我们应该直接联系,以便编排一个分阶段的SO /回答舞蹈吗?:) (3认同)
  • @Eric:我真的非常*非常感谢你对我的耐心:)谢谢!非常翔实. (2认同)

Caf*_*eek 10

因为这就是指定语言的方式.它们没有增加任何价值,为什么要包括它们?

它也与隐含类型数组非常相似

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
Run Code Online (Sandbox Code Playgroud)

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


Ree*_*sey 7

这样做是为了简化对象的构造.语言设计者没有(据我所知)明确说明为什么他们觉得这很有用,尽管它在C#Version 3.0 Specification页面中明确提到:

对象创建表达式可以省略构造函数参数列表并括起括号,前提是它包含对象或集合初始值设定项.省略构造函数参数列表并括起括号等效于指定空参数列表.

我认为他们觉得在这个例子中,为了显示开发者意图,没有必要使用括号,因为对象初始化器显示了构造和设置对象属性的意图.


Jus*_*ner 5

在第一个示例中,编译器推断您正在调用默认构造函数(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;
    }
}
Run Code Online (Sandbox Code Playgroud)

所有三个陈述都是有效的:

var obj = new SomeTest { AnotherValue = "Hello", YetAnotherValue = "World" };
var obj = new SomeTest() { AnotherValue = "Hello", YetAnotherValue = "World"};
var obj = new SomeTest("Hello") { AnotherValue = "World", YetAnotherValue = "!"};
Run Code Online (Sandbox Code Playgroud)