简体   繁体   English

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

[英]Why are C# 3.0 object initializer constructor parentheses optional?

It seems that the C# 3.0 object initializer syntax allows one to exclude the open/close pair of parentheses in the constructor when there is a parameterless constructor existing.当存在无参数构造函数时,似乎 C# 3.0 对象初始值设定项语法允许排除构造函数中的开/关括号对。 Example:示例:

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

As opposed to:与之相反:

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

I'm curious why the constructor open/close parentheses pair is optional here after XTypeName ?我很好奇为什么在XTypeName之后构造函数开/关括号对在这里是可选的?

This question was the subject of my blog on September 20th 2010 . 这个问题是我 2010 年 9 月 20 日博客的主题 Josh and Chad's answers ("they add no value so why require them?" and "to eliminate redundancy") are basically correct. Josh 和 Chad 的答案(“它们没有增加任何价值,为什么需要它们?”和“消除冗余”)基本上是正确的。 To flesh that out a bit more:再充实一点:

The feature of allowing you to elide the argument list as part of the "larger feature" of object initializers met our bar for "sugary" features.允许您将参数列表作为对象初始值设定项的“更大功能”的一部分来省略的功能满足了我们对“含糖”功能的要求。 Some points we considered:我们考虑的一些要点:

  • the design and specification cost was low设计和规格成本低
  • we were going to be extensively changing the parser code that handles object creation anyway;无论如何,我们将广泛更改处理对象创建的解析器代码; the additional development cost of making the parameter list optional was not large compared to the cost of the larger feature与更大特征的成本相比,使参数列表可选的额外开发成本并不大
  • the testing burden was relatively small compared to the cost of the larger feature与较大功能的成本相比,测试负担相对较小
  • the documentation burden was relatively small compared...相比之下,文件负担相对较小……
  • the maintenance burden was anticipated to be small;预计维护负担很小; I don't recall any bugs reported in this feature in the years since it shipped.我不记得在此功能发布后的几年中报告过任何错误。
  • the feature does not pose any immediately obvious risks to future features in this area.该功能不会对该领域的未来功能构成任何立即明显的风险。 (The last thing we want to do is make a cheap, easy feature now that makes it much harder to implement a more compelling feature in the future.) (我们最不想做的就是现在制作一个便宜、简单的功能,这使得将来实现更引人注目的功能变得更加困难。)
  • the feature adds no new ambiguities to the lexical, grammatical or semantic analysis of the language.该功能不会为语言的词汇、语法或语义分析增加新的歧义。 It poses no problems for the sort of "partial program" analysis that is performed by the IDE's "IntelliSense" engine while you are typing.它不会对您在键入时由 IDE 的“IntelliSense”引擎执行的那种“部分程序”分析造成任何问题。 And so on.等等。
  • the feature hits a common "sweet spot" for the larger object initialization feature;该功能达到了较大对象初始化功能的常见“甜蜜点”; typically if you are using an object initializer it is precisely because the constructor of the object does not allow you to set the properties you want.通常,如果你使用的是对象初始化正是因为对象的构造不允许你设置你想要的属性。 It is very common for such objects to simply be "property bags" that have no parameters in the ctor in the first place.此类对象最初只是在 ctor 中没有参数的“属性包”是很常见的。

Why then did you not also make empty parentheses optional in the default constructor call of an object creation expression that does not have an object initializer?那么为什么在没有对象初始值设定项的对象创建表达式的默认构造函数调用中也没有将空括号设为可选?

Take another look at that list of criteria above.再看看上面的标准列表。 One of them is that the change does not introduce any new ambiguity in the lexical, grammatical or semantic analysis of a program.其中之一是更改不会在程序的词汇、语法或语义分析中引入任何新的歧义。 Your proposed change does introduce a semantic analysis ambiguity:您提议的更改确实引入了语义分析歧义:

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
    }
}

Line 1 creates a new C, calls the default constructor, and then calls the instance method M on the new object.第 1 行创建一个新的 C,调用默认构造函数,然后在新对象上调用实例方法 M。 Line 2 creates a new instance of BM and calls its default constructor.第 2 行创建 BM 的新实例并调用其默认构造函数。 If the parentheses on line 1 were optional then line 2 would be ambiguous.如果第 1 行的括号是可选的,那么第 2 行将是不明确的。 We would then have to come up with a rule resolving the ambiguity;然后我们必须想出一个规则来解决歧义; we could not make it an error because that would then be a breaking change that changes an existing legal C# program into a broken program.我们不能让它成为错误,因为那将是一个破坏性的更改,将现有的合法 C# 程序更改为损坏的程序。

Therefore the rule would have to be very complicated: essentially that the parentheses are only optional in cases where they don't introduce ambiguities.因此,规则必须非常复杂:基本上,括号仅在不引入歧义的情况下才是可选的。 We'd have to analyze all the possible cases that introduce ambiguities and then write code in the compiler to detect them.我们必须分析所有可能引入歧义的情况,然后在编译器中编写代码来检测它们。

In that light, go back and look at all the costs I mention.有鉴于此,请回过头来看看我提到的所有成本。 How many of them now become large?现在有多少变大了? Complicated rules have large design, spec, development, testing and documentation costs.复杂的规则需要大量的设计、规范、开发、测试和文档成本。 Complicated rules are much more likely to cause problems with unexpected interactions with features in the future.复杂的规则更有可能导致未来与特征的意外交互出现问题。

All for what?都是为了什么? A tiny customer benefit that adds no new representational power to the language, but does add crazy corner cases just waiting to yell "gotcha" at some poor unsuspecting soul who runs into it.一个微小的客户利益,并没有给语言增加新的表现力,但确实增加了疯狂的角落案例,只是等着对遇到它的一些可怜的毫无戒心的灵魂大喊“gotcha”。 Features like that get cut immediately and put on the "never do this" list.像这样的功能会立即被删除并列入“永远不要这样做”列表。

How did you determine that particular ambiguity?你是如何确定这种特殊的歧义的?

That one was immediately clear;那一瞬间就清楚了; I am pretty familiar with the rules in C# for determining when a dotted name is expected.我非常熟悉 C# 中用于确定何时需要带点名称的规则。

When considering a new feature how do you determine whether it causes any ambiguity?在考虑一个新功能时,您如何确定它是否会导致任何歧义? By hand, by formal proof, by machine analysis, what?手工,形式证明,机器分析,什么?

All three.所有三个。 Mostly we just look at the spec and noodle on it, as I did above.大多数情况下,我们只是看看上面的规格和面条,就像我上面所做的那样。 For example, suppose we wanted to add a new prefix operator to C# called "frob":例如,假设我们想向 C# 添加一个名为“frob”的新前缀运算符:

x = frob 123 + 456;

(UPDATE: frob is of course await ; the analysis here is essentially the analysis that the design team went through when adding await .) (更新: frob当然是await ;这里的分析本质上是设计团队在添加await时经历的分析。)

"frob" here is like "new" or "++" - it comes before an expression of some sort.这里的“frob”就像“new”或“++”——它出现在某种表达式之前。 We'd work out the desired precedence and associativity and so on, and then start asking questions like "what if the program already has a type, field, property, event, method, constant, or local called frob?"我们会计算出所需的优先级和关联性等等,然后开始问这样的问题:“如果程序已经有一个类型、字段、属性、事件、方法、常量或称为 frob 的局部变量怎么办?” That would immediately lead to cases like:这将立即导致以下情况:

frob x = 10;

does that mean "do the frob operation on the result of x = 10, or create a variable of type frob called x and assign 10 to it?"这是否意味着“对 x = 10 的结果执行 frob 操作,或者创建一个名为 x 的 frob 类型的变量并为其分配 10?” (Or, if frobbing produces a variable, it could be an assignment of 10 to frob x . After all, *x = 10; parses and is legal if x is int* .) (或者,如果 frobbing 产生一个变量,它可能是 10 给frob x的赋值。毕竟, *x = 10;解析并且如果xint*是合法的。)

G(frob + x)

Does that mean "frob the result of the unary plus operator on x" or "add expression frob to x"?这是否意味着“frob 对 x 的一元加运算符的结果”或“将表达式 frob 添加到 x”?

And so on.等等。 To resolve these ambiguities we might introduce heuristics.为了解决这些歧义,我们可能会引入启发式方法。 When you say "var x = 10;"当你说“var x = 10;” that's ambiguous;这是模棱两可的; it could mean "infer the type of x" or it could mean "x is of type var".它可能意味着“推断 x 的类型”,也可能意味着“x 是 var 类型”。 So we have a heuristic: we first attempt to look up a type named var, and only if one does not exist do we infer the type of x.所以我们有一个启发式:我们首先尝试查找一个名为 var 的类型,只有当一个类型不存在时,我们才能推断出 x 的类型。

Or, we might change the syntax so that it is not ambiguous.或者,我们可能会更改语法,使其不会有歧义。 When they designed C# 2.0 they had this problem:当他们设计 C# 2.0 时,他们遇到了这个问题:

yield(x);

Does that mean "yield x in an iterator" or "call the yield method with argument x?"这是否意味着“在迭代器中yield x”或“使用参数x调用yield方法?” By changing it to通过将其更改为

yield return(x);

it is now unambiguous.现在是明确的。

In the case of optional parens in an object initializer it is straightforward to reason about whether there are ambiguities introduced or not because the number of situations in which it is permissible to introduce something that starts with { is very small .在对象初始值设定项中可选括号的情况下,可以直接推断是否引入了歧义,因为允许引入以 { 开头的内容的情况非常少 Basically just various statement contexts, statement lambdas, array initializers and that's about it.基本上只是各种语句上下文、语句 lambda、数组初始值设定项,仅此而已。 It's easy to reason through all the cases and show that there's no ambiguity.很容易通过所有案例进行推理并表明没有歧义。 Making sure the IDE stays efficient is somewhat harder but can be done without too much trouble.确保 IDE 保持高效有点困难,但可以轻松完成。

This sort of fiddling around with the spec usually is sufficient.这种对规范的摆弄通常就足够了。 If it is a particularly tricky feature then we pull out heavier tools.如果这是一个特别棘手的功能,那么我们会使用更重的工具。 For example, when designing LINQ, one of the compiler guys and one of the IDE guys who both have a background in parser theory built themselves a parser generator that could analyze grammars looking for ambiguities, and then fed proposed C# grammars for query comprehensions into it;例如,在设计 LINQ 时,一名编译器人员和一名 IDE 人员都具有解析器理论背景,他们自己构建了一个解析器生成器,可以分析语法以寻找歧义,然后将提议的 C# 语法输入其中以进行查询理解; doing so found many cases where queries were ambiguous.这样做发现了许多查询不明确的情况。

Or, when we did advanced type inference on lambdas in C# 3.0 we wrote up our proposals and then sent them over the pond to Microsoft Research in Cambridge where the languages team there was good enough to work up a formal proof that the type inference proposal was theoretically sound.或者,当我们在 C# 3.0 中对 lambdas 进行高级类型推断时,我们写下了我们的建议,然后将它们通过池塘发送到剑桥的微软研究院,那里的语言团队足够优秀,可以提出类型推断建议的正式证明理论上是健全的。

Are there ambiguities in C# today?今天在 C# 中有歧义吗?

Sure.当然。

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

In C# 1 it is clear what that means.在 C# 1 中很清楚这意味着什么。 It's the same as:它与以下内容相同:

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

That is, it calls G with two arguments that are bools.也就是说,它使用两个布尔值参数调用 G。 In C# 2, that could mean what it meant in C# 1, but it could also mean "pass 0 to the generic method F that takes type parameters A and B, and then pass the result of F to G".在 C# 2 中,这可能意味着它在 C# 1 中的含义,但也可能意味着“将 0 传递给采用类型参数 A 和 B 的泛型方法 F,然后将 F 的结果传递给 G”。 We added a complicated heuristic to the parser which determines which of the two cases you probably meant.我们向解析器添加了一个复杂的启发式方法,以确定您可能指的是两种情况中的哪一种。

Similarly, casts are ambiguous even in C# 1.0:同样,即使在 C# 1.0 中,强制转换也是不明确的:

G((T)-x)

Is that "cast -x to T" or "subtract x from T"?那是“将 -x 转换为 T”还是“从 T 中减去 x”? Again, we have a heuristic that makes a good guess.同样,我们有一个启发式方法可以做出很好的猜测。

Because that's how the language was specified.因为这就是语言的指定方式。 They add no value, so why include them?它们没有任何价值,那么为什么要包含它们呢?

It's also very similar to implicity typed arrays它也与隐式类型数组非常相似

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

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

This was done to simplify the construction of objects.这样做是为了简化对象的构造。 The language designers have not (to my knowledge) specifically said why they felt that this was useful, though it is explicitly mentioned in the C# Version 3.0 Specification page :语言设计者并没有(据我所知)特别说明为什么他们认为这很有用,尽管在C# 版本 3.0 规范页面中明确提到了这一点:

An object creation expression can omit the constructor argument list and enclosing parentheses, provided it includes an object or collection initializer.对象创建表达式可以省略构造函数参数列表和括号,前提是它包含对象或集合初始值设定项。 Omitting the constructor argument list and enclosing parentheses is equivalent to specifying an empty argument list.省略构造函数参数列表和括起来的括号等效于指定一个空参数列表。

I suppose that they felt the parenthesis, in this instance, were not necessary in order to show developer intent, since the object initializer shows the intent to construct and set the properties of the object instead.我想在这种情况下,他们认为括号不是为了显示开发人员意图所必需的,因为对象初始值设定项显示了构造和设置对象属性的意图。

In your first example, the compiler infers that you're calling the default constructor (the C# 3.0 Language Specification states that if no parenthesis are provided, the default constructor is called).在您的第一个示例中,编译器推断您正在调用默认构造函数(C# 3.0 语言规范指出,如果未提供括号,则调用默认构造函数)。

In the second, you explicitly call the default constructor.在第二个中,您显式调用默认构造函数。

You can also use that syntax to set properties while explicitly passing values to the constructor.您还可以使用该语法来设置属性,同时将值显式传递给构造函数。 If you had the following class definition:如果您有以下类定义:

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;
    }
}

All three statements are valid:所有三个陈述都是有效的:

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

I am no Eric Lippert, so I can't say for sure, but I would assume it is because the empty parenthesis is not needed by the compiler in order to infer the initialization construct.我不是 Eric Lippert,所以我不能肯定,但我认为这是因为编译器不需要空括号来推断初始化构造。 Therefore it becomes redundant information, and not needed.因此,它成为冗余信息,不需要。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM