C# 12 引入集合表达式(collection expressions),这是一种使用新的简洁语法来创建集合。本文介绍了使用方法和创建支持集合表达式的自定义集合类型。

用法

通过元素创建集合

使用形如 <type> <name> = [<e1>, <e2>, <e3>] 的方式创建集合,例如

C#
Span<int> span = [1, 3, 5, 7, 9];
ReadOnlySpan<string> days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
double[] array = [1.1, 2.2];
List<double> list = [3.3, 4.4];

也可以是空集合,例如

C#
Span<int> span = [];
ReadOnlySpan<string> days = [];
double[] array = [];
List<double> list = [];

也可以是接口,例如

C#
IEnumerable<int> ienumerable = [1, 3, 5, 7, 9];
IList<int> ilist = [0, 2, 4, 6, 8];

也可以方法调用,例如

C#
using System.Collections.Generic;
using System.Linq;

public int Sum(IEnumerable<int> data) => data.Sum();

Sum([1, 2, 3, 4, 5]);

展开元素

使用展开操作 .. 将其他集合的值内联到集合中,形如 [e1, ..c2, e2, ..c2],例如

C#
string[] weekdays = ["Mon", "Tue", "Wed", "Thu", "Fri"];
string[] weekend = ["Sat", "Sun"];

// ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
string[] daysOfWeek = [..weekdays, ..weekend];

char[] hello = ['h', 'e', 'l', 'l', 'o'];
char[] world = ['w', 'o', 'r', 'l', 'd'];

// ['h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!']
char[] helloWorld = [..hello, ',', ' ', ..world, '!'];

创建

主要有两种方式来创建可以使用集合表达式的集合

  • 该集合可以使用集合初始化器(collection initializers)
  • 该集合具有适当的 Create Method。【推荐】

集合初始化器方式

集合初始化器需要满足如下条件

  • 具有无参构造函数
  • 实现 IEnumerable
  • 具有适当的 Add 实例方法或扩展方法。

例如

C#
public class MyCollection: IEnumerable
{
    private List<int> _list = new();
    
    public IEnumerator GetEnumerator() => _list.GetEnumerator();
    
    public void Add(int num) => _list.Add(num);
}

我们可以使用集合初始化器

C#
MyCollection mc = new() {1, 3, 5};

我们也可以使用集合表达式

C#
MyCollection mc = [1, 3, 5];

也可以是泛型的,例如

C#
MyCollection<int> mc1 = [1, 3, 5];
MyCollection<char> mc2 = ['a', 'b'];

public class MyCollection<T>: IEnumerable
{
    private List<T> _list = new();
    
    public IEnumerator GetEnumerator() => _list.GetEnumerator();
    
    public void Add(T element) => _list.Add(element);
}

但是注意并非所有集合初始化器都支持集合表达式,它们的区别主要是,对于集合表达式而言必须有一个仅一个参数即可调用的 Add 方法,而集合初始化器可以有多个参数。例如 Dictionary<TKey,TValue>Add(TKey, TValue) 方法必须要有两个参数,因此 Dictionary<TKey,TValue> 不支持集合表达式仅支持集合初始化器。

Create Method 方式

需要满足如下条件

  • 集合类型需要有 [CollectionBuilder(Type builderType, string methodName)] 属性,其中 builderType 必须是非泛型 classstructmethodName 指向 Create Method
    • Create Method 必须是 static
    • Create Method 必须仅有一个 ReadOnlySpan<T> 类型的参数
  • 集合类型需要有 GetEnumerator() 实例方法

例如

C#
[CollectionBuilder(typeof(MyCollection), nameof(Create))]
public class MyCollection
{
    private int[] _list;
    
    public MyCollection(ReadOnlySpan<int> data) => _list = data.ToArray();
    
    // Create Method
    public static MyCollection Create(ReadOnlySpan<int> data) => new(data);
    
    public IEnumerator<int> GetEnumerator() => _list.AsEnumerable().GetEnumerator();
}

可以使用集合表达式

C#
MyCollection mc = [1, 3, 5];

这里有些需要注意的是

  • 集合类型需要有 GetEnumerator() 方法,但不必实现 IEnumerableIEnumerable<T> 接口,也就是说这是一个鸭子类型
  • Create Method 的参数类型 ReadOnlySpan<T> 中的类型 T 必须和集合类型的元素类型保持一致,而集合类型的元素类型是根据 GetEnumerator() 的返回值类型确定的,若返回 IEnumerator 则元素类型为 object,若返回 IEnumerator<T> 则元素类型为 T

也可以是泛型的,例如

C#
MyCollection<int> mc1 = [1, 3, 5];
MyCollection<char> mc2 = ['a', 'b'];

public class MyCollection
{
    // Create Method
    public static MyCollection<T> Create<T>(ReadOnlySpan<T> data) => new(data);
}

[CollectionBuilder(typeof(MyCollection), nameof(MyCollection.Create))]
public class MyCollection<T>
{
    private T[] _list;
    
    public MyCollection(ReadOnlySpan<T> data) => _list = data.ToArray();
    
    public IEnumerator<T> GetEnumerator() => _list.AsEnumerable().GetEnumerator();
}

上述示例均为最小实现,一般情况下集合类型需要继承 IEnumerable<T> 以使用 Linq,完整示例如下

C#
MyCollection<int> mc1 = [1, 3, 5];
MyCollection<char> mc2 = ['a', 'b'];

public class MyCollection
{
    public static MyCollection<T> Create<T>(ReadOnlySpan<T> data) => new(data);
}

[CollectionBuilder(typeof(MyCollection), nameof(MyCollection.Create))]
public class MyCollection<T>: IEnumerable<T>
{
    private T[] _list;
    
    public MyCollection(ReadOnlySpan<T> data) => _list = data.ToArray();
    
    IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator();
    
    public IEnumerator<T> GetEnumerator() => _list.AsEnumerable().GetEnumerator();
}

接口实现

接口实现一般采用 Create Method 方式,在上述基础上,添加一个接口的Create Method 方式即可,例如

C#
IMyCollection<int> mc1 = [1, 3, 5];
IMyCollection<char> mc2 = ['a', 'b'];

public class MyCollection
{
    public static MyCollection<T> Create<T>(ReadOnlySpan<T> data) => new(data);
}

[CollectionBuilder(typeof(MyCollection), nameof(MyCollection.Create))]
public class MyCollection<T>: IMyCollection<T>
{
    private T[] _list;
    
    public MyCollection(ReadOnlySpan<T> data) => _list = data.ToArray();
    
    public IEnumerator<T> GetEnumerator() => _list.AsEnumerable().GetEnumerator();
}

[CollectionBuilder(typeof(MyCollection), nameof(MyCollection.Create))]
public interface IMyCollection<T>
{
    // 一般情况下,继承 IEnumerable<T> 即可
    // 但是若未继承 IEnumerable<T> ,则必须要有该方法,以确定集合接口的元素类型
    IEnumerator<T> GetEnumerator();
}