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
必须是非泛型class
或struct
,methodName
指向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()
方法,但不必实现IEnumerable
或IEnumerable<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();
}