为什么我可以像C#中的数组一样初始化列表

本文关键字:一样 列表 初始化 数组 我可以 为什么 | 更新日期: 2023-09-27 18:20:18

今天我惊讶地发现在C#中我可以做:

List<int> a = new List<int> { 1, 2, 3 };

为什么我可以这样做?什么是构造函数?我如何用自己的课程做到这一点?我知道这是初始化数组的方法,但数组是语言项,列表是简单的对象。。。

为什么我可以像C#中的数组一样初始化列表

这是.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)

它被称为句法糖。

List<T>是一个"简单"类,但编译器对它进行了特殊处理,以使您的生活更轻松。

这就是所谓的集合初始值设定项。您需要实现IEnumerable<T>Add方法。

根据C#版本3.0规范"应用集合初始值设定项的集合对象的类型必须是仅为一个T实现System.Collections.Generic.ICollection的类型。"

然而,截至本文撰写之时,该信息似乎不准确;请参阅下面评论中Eric Lippert的澄清。

它的工作要归功于集合初始化器,这些初始化器基本上需要集合实现Add方法,而Add方法将为您完成工作。

集合初始化器的另一个很酷的地方是,可以有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的方法,返回类型可以是任何类型。

类似数组的语法正在一系列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()调用,这些调用即使在只读字段上也是完全合法的。