为什么我可以在C#中像初始化数组一样初始化List?

131
今天我惊奇地发现在C#中我可以这样做:
List<int> a = new List<int> { 1, 2, 3 };

我为什么可以这样做?调用了哪个构造函数?我如何使用自己的类来做到这一点?我知道这是初始化数组的方法,但数组是语言项,而列表是简单对象...


1
这个问题可能会有帮助:https://dev59.com/_UrSa4cB1Zd3GeqPVlgu - Bob Kaufman
10
很棒,不是吗?你也可以使用非常相似的代码来初始化字典:{ { "key1", "value1"}, { "key2", "value2"} } - danludwig
从另一个角度来看,这种数组初始化语法提醒我们,List<int> 的底层数据类型实际上只是一个数组。我讨厌 C# 团队没有将其命名为 ArrayList<T>,这听起来是如此明显和自然的事情。 - RBT
6个回答

183
这是.NET中集合初始化器语法的一部分。只要以下条件被满足,您可以在创建的任何集合上使用此语法:
  • 它实现了IEnumerable(最好是IEnumerable<T>

  • 它有一个名为Add(...)的方法

默认构造函数会被调用,然后对初始化器的每个成员调用Add(...) 方法。
因此,这两个代码块大致相同:
List<int> a = new List<int> { 1, 2, 3 };

并且
List<int> temp = new List<int>();
temp.Add(1);
temp.Add(2);
temp.Add(3);
List<int> a = temp;

如果你想要调用一个备用构造函数,例如为了在扩展 List<T> 时防止过度调整大小等情况,你是可以这样做的:

// Notice, calls the List constructor that takes an int arg
// for initial capacity, then Add()'s three items.
List<int> a = new List<int>(3) { 1, 2, 3, }

请注意,Add()方法不需要只接受一个项目。例如,Dictionary<TKey, TValue>Add() 方法接受两个项目。
var grades = new Dictionary<string, int>
    {
        { "Suzy", 100 },
        { "David", 98 },
        { "Karen", 73 }
    };

大致相同于:

var temp = new Dictionary<string, int>();
temp.Add("Suzy", 100);
temp.Add("David", 98);
temp.Add("Karen", 73);
var grades = temp;

因此,要将此添加到您自己的类中,您只需要像上面提到的那样实现IEnumerable(最好是IEnumerable<T>),并创建一个或多个Add()方法:

public class SomeCollection<T> : IEnumerable<T>
{
    // implement Add() methods appropriate for your collection
    public void Add(T item)
    {
        // your add logic    
    }

    // implement your enumerators for IEnumerable<T> (and IEnumerable)
    public IEnumerator<T> GetEnumerator()
    {
        // your implementation
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

然后,您可以像使用BCL集合一样使用它:
public class MyProgram
{
    private SomeCollection<int> _myCollection = new SomeCollection<int> { 13, 5, 7 };    

    // ...
}

(更多信息请参见MSDN)

29
回答不错,但我注意到在第一个例子中说“exactly identical”并不是完全准确的。它只与 List<int> temp = new List<int>(); temp.Add(1); ... List<int> a = temp; 完全相同,也就是说,在调用所有添加操作之后才会初始化a变量。否则,就可以合法地做出像 List<int> a = new List<int>() { a.Count, a.Count, a.Count }; 这样疯狂的事情了。 - Eric Lippert
1
@user606723:List<int> a; a = new List<int>() { a.Count };List<int> a = new List<int>() { a.Count };之间没有实质性的区别。 - Joren
4
@Joren: 你是正确的,事实上C#规范指出T x = y;T x; x = y;是相同的。这个事实可能会导致一些奇怪的情况。例如,int x = M(out x) + x;是完全合法的,因为int x; x = M(out x) + x;是合法的。 - Eric Lippert
1
@JamesMichaelHare 不需要实现“IEnumerable<T>”;非泛型的“IEnumerable”就足够允许使用集合初始化语法。 - phoog
2
如果你使用实现了 IDisposable 接口的某种集合,那么 Eric 的观点非常重要。如果 using (var x = new Something{ 1, 2 }) 中的一个 Add 调用失败,那么对象不会被释放。 - porges
显示剩余5条评论

11
所谓“语法糖”指的是语法上的简化List<T> 是一个“简单”的类,但编译器会对它进行特殊处理,以方便你的使用。
这里说的是集合初始化器。你需要实现 IEnumerable<T>Add 方法。

8
根据《C# Version 3.0 Specification》(链接)指出,“应用集合初始化程序的集合对象必须是实现了 System.Collections.Generic.ICollection 的类型,而且类型恰好为一个T。”然而,根据本文撰写时的情况,此信息似乎不准确,请参见Eric Lippert在下面的评论中的澄清。

10
那个页面不准确。感谢您指出这一点。那可能是一些初步的预发布文档,一些该删除却未被删除的文档。最初的设计需要使用ICollection,但我们最终确定的设计并非如此。 - Eric Lippert

6

这是由 集合初始化器 实现的,基本上需要集合实现一个Add方法,然后就可以为您完成操作了。


6

集合初始化程序的另一个酷炫之处在于,您可以有多个 Add 方法的重载,并且可以在同一个初始化程序中调用它们所有的方法!例如:

public class MyCollection<T> : IEnumerable<T>
{
    public void Add(T item, int number)
    {

    }
    public void Add(T item, string text) 
    {

    }
    public bool Add(T item) //return type could be anything
    {

    }
}

var myCollection = new MyCollection<bool> 
{
    true,
    { false, 0 },
    { true, "" },
    false
};

它调用正确的重载方法。同时,它仅寻找名称为Add的方法,返回类型可以是任何类型。

0

数组式语法被转换为一系列的Add()调用。

为了在一个更有趣的例子中看到这个,考虑以下代码,我做了两件有趣的事情,听起来在C#中首先是非法的,1)设置只读属性,2)使用类似数组的初始化器设置列表。

public class MyClass
{   
    public MyClass()
    {   
        _list = new List<string>();
    }
    private IList<string> _list;
    public IList<string> MyList 
    { 
        get
        { 
            return _list;
        }
    }
}
//In some other method
var sample = new MyClass
{
    MyList = {"a", "b"}
};

这段代码将完美地工作,尽管1)MyList是只读的,2)我使用数组初始化器设置了一个列表。

之所以能够正常工作,是因为在对象初始化器的代码中,编译器总是将任何类似{}的语法转换为一系列Add()调用,即使在只读字段上也是完全合法的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接