【问题标题】:covariant object initializers?协变对象初始化器?
【发布时间】:2011-01-23 12:52:04
【问题描述】:

假设我有一个类,它的属性是字典,使用对象初始化器我可以使用这种语法(我认为它看起来很干净):

new MyClass()
{
  Table = { {"test",true},{"test",false} }
}

但是,在初始化程序之外我不能这样做:

this.Table = { {"test",true},{"test",false} };

为什么初始化器是一个特例?我冒昧地猜测它与 LINQ 要求、协方差或诸如此类的东西有关,但感觉有点不一致,不能在任何地方使用那种初始化程序......

【问题讨论】:

  • 我认为编译器错误是“预期表达式”这一事实是一个很大的线索。在第二个示例中,语法并不像您通常期望的那样指示表达式,即没有 new 运算符。我怀疑第一个示例可以正常工作,因为它是一种特殊情况,并且编译器在语法上对表达式的构成更加放松。宽松规则的好处是更简洁的语法,这对于对象初始化器语法的上下文来说是非常可取的,否则它看起来会很丑。

标签: c# .net object-initializers


【解决方案1】:

这个问题有点令人困惑,因为这个问题与 LINQ 无关,与泛型方差无关,并且具有集合初始化器和对象初始化器。真正的问题是,据我所知“为什么在对象创建表达式之外使用集合初始化器是不合法的?

这里的相关设计原则是,一般来说,我们希望创建和初始化对象的操作在其中某处包含“新”一词,作为向读者发出的信号,表明这里正在创建对象。 (是的,在 C# 中这条规则有一些例外。作为给读者的练习,看看你是否可以将它们全部命名。)

按照自己的方式做事会使对代码的推理变得更加困难。快,这是做什么的?

d = new List<int>() { 10, 20, 30 };
d = { 40, 50, 60 };

第二行是否追加 40、50、60 到现有列表?还是用新列表替换旧列表?那里没有“新”,所以读者是否期望已经创建了一个新对象?

当你说

q = new Whatever() { MyList = { 40, 50, 60 } };

不会创建新列表;它将 40、50、60 附加到构造函数分配的现有列表中。因此,您建议的语法对于是否创建新列表是模棱两可和令人困惑的。

提议的功能既令人困惑又不必要,因此不太可能很快实施。

【讨论】:

  • @lazyberezovsky:这是我提到的特殊情况之一。请注意,它在声明的初始化程序中起作用。至于为什么 C# 设计委员会真的做了或没有做某事的问题,我怀疑我对这件事的了解比你有点,就像我一样他们的设计笔记和我在过去五年中每周与至少两名原始设计师讨论语言设计六到八小时。
  • 如果我是 Eric,我会这样对lazyberezovsky 说:“你知道我是谁吗?” ;)
  • @Lazyberezovsky:首先,是的,我确实回答了这个问题。答案是“因为代码的读者会混淆其意图是改变现有对象还是新对象”。第二,当问题是“语言设计团队在想什么?”那么我认为回答问题的人是否在语言设计团队中并不重要。你不同意吗?也许您可以解释为什么您认为 C# 设计者不认为“new”关键字是向读者发出的信号。您的意见基于什么证据?
  • @lazyberezovsky:您的意思是,当读者阅读“d = {40, 50, 60}”时,他们首先想到的是“它所指的集合是被附加到“?这不是脑海中浮现的第一件事。当我看到“d = something”时,我认为变量 d 的内容正在被替换。我想大多数人都会同意。
  • @lazyberezovsky 我想如果你看到 "d += {40, 50, 60}" 你会有一个附加到集合的案例,但不是 "d = {40, 50, 60 }”。 @Eric Lippert:我知道功能从 -100 万的成本开始;)但是你们可以考虑集合的语法“d += {40, 50, 60}”吗?
【解决方案2】:

此限制比 LINQ 早得多。即使回到 C 语言中,您也可以编写

int numbers[5] = {1, 2, 3, 4, 5};

但您不能使用此语法为数组赋值。

我对 C# 中这背后的原因的猜测是,通常你不应该对两个不同的对象使用相同的引用。如果您需要将新集合分配给现有引用,很可能您没有很好地设计代码,您可以在定义时初始化集合,或者使用两个单独的引用而不是一个。

【讨论】:

  • 据我所知,这与 Linq 无关。以及如何对两个不同的对象使用相同的引用?
  • @chibacity:这就是我说的。 mko 认为这与 LINQ 有关,我相信它没有。如果将一个集合分配给它,然后再分配另一个集合,则可以对两个对象使用相同的引用,如下所示:col = new Dictionary&lt;string,bool&gt;(...)。我认为这会使代码看起来更糟,最好使用新的参考。
  • @Ilya 我明白了。引入 C 角度只是令人困惑。C# 也有数组初始化器,但上面不是数组初始化器的示例,它是一个集合初始化器。完全不同。
  • @chibacity 当然,C# 中的集合和 C 中的数组不是一回事,但语法非常相似。在 C# 中比较:int[] n1 = new int[4] {2, 4, 6, 8}; 对不起,我的答案中的语法错误,我现在编辑它。
  • @Ilya 我不怀疑语法让人想起,但这不能作为解释的基础——它只是文本,看起来很相似。
【解决方案3】:

考虑到您的语法会在运行时抛出 NullReferenceException - 您确定可以使用它吗?

public class Test
{
  public Dictionary<string, bool> Table {get; set;}
}

public void TestMethod()
{
  Test t = new Test { Table = { {"test", false} } }; //NullReferenceException
}

这编译成以下内容(通过反射器):

Test <>g__initLocal3 = new Test();
<>g__initLocal3.Table.Add("test", 0.0M);

如您所见,Table 未初始化,因此在运行时生成NullReferenceException

如果您在 Test 的 ctor 中创建 Dictionary,则类初始化程序会生成 Add 语句的级联,这是初始化程序中的语法糖(对于 IEnumerables)。

由于我们无法看到或想象的不可预见的副作用,这可能不是针对普通代码引入的。 Eric Lippert 或许能够提供帮助,因为他可能对手头的事情有更多的了解。

【讨论】:

  • 是的,我知道,但语法不完全相同
  • 但他不是在问为什么你必须在它前面提供new Dictionary&lt;string, bool&gt;
  • 如果在 Test 构造函数或其他地方创建字典,它不会抛出异常。
  • 如果集合不是在构造函数中创建或直接在为 true 的字段中创建,则会出现异常
猜你喜欢
  • 2013-06-27
  • 1970-01-01
  • 2013-04-05
  • 1970-01-01
  • 2013-07-04
  • 1970-01-01
  • 1970-01-01
  • 2012-04-20
  • 1970-01-01
相关资源
最近更新 更多