似乎C#3.0对象初始化程序语法允许在存在无参数构造函数的情况下在构造函数中排除括号的打开/关闭对。例:
var x = new XTypeName { PropA = value, PropB = value };
相对于:
var x = new XTypeName() { PropA = value, PropB = value };
我很好奇为什么构造函数打开/关闭括号对在此之后是可选的XTypeName
?
似乎C#3.0对象初始化程序语法允许在存在无参数构造函数的情况下在构造函数中排除括号的打开/关闭对。例:
var x = new XTypeName { PropA = value, PropB = value };
相对于:
var x = new XTypeName() { PropA = value, PropB = value };
我很好奇为什么构造函数打开/关闭括号对在此之后是可选的XTypeName
?
Answers:
这个问题是我2010年9月20日发表的博客的主题。乔什和乍得的答案(“它们没有任何价值,为什么需要它们?”和“消除冗余”)基本上是正确的。要充实一点:
作为对象初始化程序的“较大功能”的一部分,允许您省去参数列表的功能符合我们的“必需”功能标准。我们考虑了以下几点:
那么,为什么在没有对象初始值设定项的对象创建表达式的默认构造函数调用中也没有将空括号设为可选?
再看看上面的标准列表。其中之一是更改不会在程序的词法,语法或语义分析中引入任何新的歧义。您提出的更改确实引入了语义分析的歧义:
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行创建BM的新实例并调用其默认构造函数。如果第1行上的括号是可选的,则第2行将是不明确的。 然后,我们将不得不提出解决歧义的规则;我们无法将其设为错误,因为这将是一个重大更改,将现有的合法C#程序更改为损坏的程序。
因此,规则必须非常复杂:实质上,括号仅在不引起歧义的情况下才是可选的。我们必须分析所有可能引起歧义的情况,然后在编译器中编写代码以检测它们。
有鉴于此,请返回并查看我提到的所有费用。现在有多少个变大了?复杂的规则需要大量的设计,规格,开发,测试和文档编制成本。复杂的规则将来很有可能会引起与功能的意外交互问题。
都是为了什么?微小的客户利益并没有给语言增加新的表示能力,但是确实增加了疯狂的极端情况,只是等待对遇到它的一些可怜的毫无戒心的人大喊“陷阱”。诸如此类的功能会立即被删除,并放在“永不执行此操作”列表中。
您如何确定这种特殊的歧义?
那是立即清楚的;我非常熟悉C#中用于确定何时应使用虚线名称的规则。
考虑新功能时,如何确定它是否引起歧义?手工,形式证明,机器分析是什么?
三个全部。通常,就像我在上面所做的那样,我们只是看一下规格和面条。例如,假设我们想向C#添加一个名为“ frob”的新前缀运算符:
x = frob 123 + 456;
(更新:frob
当然是await
;这里的分析本质上是设计团队在添加时进行的分析await
。)
“ frob”在这里就像“ new”或“ ++”一样-在某种表达式之前。我们将计算出所需的优先级和关联性,等等,然后开始问诸如“程序是否已经具有称为frob的类型,字段,属性,事件,方法,常量或局部变量?”之类的问题。这将立即导致以下情况:
frob x = 10;
这是否意味着“对x = 10的结果执行frob运算,或创建名为x的frob类型变量并为其分配10?” (或者,如果frobbing产生一个变量,则可能是10的赋值frob x
。毕竟,*x = 10;
如果x
is则解析并合法)int*
。
G(frob + x)
这是否意味着“在x上添加一元加运算符的结果”或“在x上添加表达式frob”?
等等。为了解决这些歧义,我们可能会引入启发式方法。当您说“ var x = 10;”时 那是模棱两可的;它可能表示“推断x的类型”,也可能表示“ x属于var类型”。因此,我们进行了试探:我们首先尝试查找一个名为var的类型,并且只有在不存在这种类型的情况下,我们才能推断x的类型。
或者,我们可能会更改语法,以使其不模糊。在设计C#2.0时,他们遇到了以下问题:
yield(x);
这是否意味着“迭代器中的yield x”或“使用参数x调用yield方法?” 通过将其更改为
yield return(x);
现在是明确的。
对于对象初始化器中的可选括号,可以直接推断是否引入歧义,因为允许引入以{开头的事物的情况的数量非常小。基本上只是各种语句上下文,语句lambda,数组初始化程序,仅此而已。在所有情况下都很容易推理,并表明没有歧义。确保IDE保持高效的难度要大一些,但是可以轻松完成。
这种对规范的摆弄通常就足够了。如果这是一个特别棘手的功能,那么我们将淘汰较重的工具。例如,在设计LINQ时,其中一位具有解析器理论背景的编译器人员和一位IDE人员都构建了自己的解析器生成器,可以分析语法以寻找歧义,然后将提议的C#语法馈入其中以进行查询理解。 ; 这样做会发现许多情况下查询不明确。
或者,当我们在C#3.0中对lambda进行高级类型推断时,我们写了我们的建议,然后将它们发送给了剑桥的Microsoft Research,那里的语言团队有足够的能力来正式证明类型推断建议是理论上合理。
今天的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”?同样,我们有一个很好的猜测方法。
因为那是指定语言的方式。它们没有任何价值,那么为什么要包括它们呢?
它也与隐式类型数组非常相似
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
这样做是为了简化对象的构造。语言设计师没有(据我所知)没有特别说明为什么他们觉得这很有用,尽管在C#3.0版规范页面中明确提到了这一点:
如果对象创建表达式包含对象或集合初始化程序,则它可以省略构造函数参数列表和括号。省略构造函数参数列表并包含括号等效于指定一个空的参数列表。
我想在这种情况下,他们认为不需要括号来显示开发人员的意图,因为对象初始化程序显示了构造和设置对象属性的意图。
在第一个示例中,编译器推断您正在调用默认构造函数(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 = "!"};