在 Visual Studio C# 编译器中是否应该将此列表初始化行为报告为错误?

Should this list initializer behavior be reported as bug in the Visual Studio C# Compiler?

首先我要说的是我已经改变了我的设计并且不再需要它但是得到一个好的答案仍然很好

我的代码中有以下class,ListContainer,(附上的代码都是mcve):

class ListContainer
{
    public object ContainedList
    {
        get; private set;
    }

    public int Value
    {
        get; private set;
    }

    public ListContainer(object list, int value)
    {
        ContainedList = list;
        Value = value;
    }
}

在我的代码中的其他一些 class 中,我有一个 List<ListContainer>,我需要每个 ListContainer 来包含这个 List<ListContainer>,所以我可以那样实现它:

//Field in the class
List<ListContainer> mContainers = null;

//In the constructor:
mContainers = new List<ListContainer>();
mContainers.Add(new ListContainer(mContainers, SOME_CONST));
mContainers.Add(new ListContainer(mContainers, SOME_OTHER_CONST));

它工作正常,但是当我尝试使用 list initializer:

//Field in the class
List<ListContainer> mContainers = null;

//In the constructor:
mContainers = new List<ListContainer>
{
    new ListContainer(mContainers, SOME_CONST),
    new ListContainer(mContainers, SOME_OTHER_CONST)
}

您可能希望结果相同,但实际上结果如下所示:

mContainers
    [0] - ListContainer
              ContainedList = null
              Value = SOME_CONST
    [1] - ListContainer
              ContainedList = null
              Value = SOME_OTHER_CONST

看到这个结果,我检查了这个 C# 编译的输出 MSIL 并看到了以下代码:

现在,这解释了为什么会出现问题,我什至在 CSharp Language Specification 文档中查看过,这是定义的行为:

A List can be created and initialized as follows:

var contacts = new List<Contact> {
    new Contact {
        Name = "Chris Smith",
        PhoneNumbers = { "206-555-0101", "425-882-8080" }
    },
    new Contact {
        Name = "Bob Harris",
        PhoneNumbers = { "650-555-0199" }
    }
};

which has the same effect as

var __clist = new List<Contact>();
Contact __c1 = new Contact();
__c1.Name = "Chris Smith";
__c1.PhoneNumbers.Add("206-555-0101");
__c1.PhoneNumbers.Add("425-882-8080");
__clist.Add(__c1);
Contact __c2 = new Contact();
__c2.Name = "Bob Harris";
__c2.PhoneNumbers.Add("650-555-0199");
__clist.Add(__c2);
var contacts = __clist;

where __clist, __c1 and __c2 are temporary variables that are otherwise invisible and inaccessible.

很明显,这种行为是有意为之的。一切都在临时变量而不是原始变量上完成是否有充分的理由?因为这对我来说似乎是一种错误的行为。

首先在 = 的右侧评估赋值,然后进行赋值。所以 mContainers 仍然是空的。

原因是为了避免并发线程访问要添加元素的原始变量的竞争条件。如果线程访问变量时尚未向其添加所有元素,则会出现不一致。

访问同一个变量的两个线程因此会得到一个不一致的列表,其中包含不同的元素。

如果将元素添加到不同的行,这不会让人感到震惊,但由于您使用了对象初始化器,因此通常会认为对象是直接用其所有元素初始化的,因此需要一个临时的、不可见的、可变的。

Is there a good reason everything is done on the temporary List and not on the original one?

没有原始列表:

var __clist = new List<Contact>();
// …
__clist.Add(__c1);
// …
__clist.Add(__c2);
var contacts = __clist;

只创建了一个列表。你的意思可能是它是在一个临时变量而不是原始变量上完成的,但这没有实际效果——除了可能更容易实现之外。如果您认为集合初始化不限于变量赋值的上下文,则尤其如此:

SomeMethodCall(new List<int>() { 1, 2, 3 });

由于没有对该列表的引用,实现它的最简单解决方案就是使用一个保存列表的临时变量,然后将该变量放在初始化程序的位置。

同样重要的是旧值被完全覆盖。因此,在您的 mContainers = new List<ListContainer> 中,mContainers 的旧值永远不会出于初始化语法的目的而被查看。

将集合初始化视为“原子”操作可能是个好主意。该列表仅在整个初始化程序完成后才存在(至少对您而言)。所以你不能从初始化器中引用它自己。