C#中如何为不可变集合实现集合初始化语法且不破坏不可变性
不可变集合支持集合初始化语法的实现方案
目前有两种成熟的实现方案,可在保障集合不可变性的前提下保留初始化语法的简洁性:
方案1:Builder模式+隐式转换(兼容C# 6及以上所有版本)
核心思路是将可变的Add方法移到嵌套的Builder类中,不可变集合本身不对外暴露任何修改接口,通过隐式转换自动将初始化完成的Builder实例转为不可变集合。
实现代码:
class MyElement { public MyElement() { } public MyElement(string label) => Label = label; public MyElement(int code, string description) => Label = $"{code}:{description}"; public string Label { get; set; } } class MyImmutableCollection : IEnumerable<MyElement> { private readonly IReadOnlyList<MyElement> _list; // 仅内部可用的构造函数 internal MyImmutableCollection(List<MyElement> list) { _list = list.AsReadOnly(); } // 仅暴露读操作 public MyElement this[int index] => _list[index]; public int Count => _list.Count; #region IEnumerable实现 public IEnumerator<MyElement> GetEnumerator() => _list.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); #endregion // 嵌套Builder类,专门用于初始化 public class Builder : IEnumerable<MyElement> { private readonly List<MyElement> _list = new(); // 所有Add重载都放在Builder中 public void Add(MyElement element) => _list.Add(element); public void Add(string label) => Add(new MyElement(label)); public void Add(int code, string description) => Add(new MyElement(code, description)); #region IEnumerable实现(满足集合初始化语法要求) public IEnumerator<MyElement> GetEnumerator() => _list.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); #endregion // 隐式转换为不可变集合 public static implicit operator MyImmutableCollection(Builder builder) { return new MyImmutableCollection(builder._list); } } }
使用效果:
// 和期望的初始化语法几乎一致,最终得到的是完全不可变的集合 MyImmutableCollection collection1 = new MyImmutableCollection.Builder { new MyElement() }; MyImmutableCollection collection2 = new MyImmutableCollection.Builder { "ABC" }; MyImmutableCollection collection3 = new MyImmutableCollection.Builder { { 1, "One" }, { 2, "Two" } };
最终得到的MyImmutableCollection实例没有任何公开的修改方法,彻底保障不可变性。
方案2:C# 12+ 集合表达式原生支持
如果项目使用C# 12及以上版本,可以通过CollectionBuilder特性直接获得原生集合表达式的支持,代码更简洁。
实现代码:
using System.Runtime.CompilerServices; [CollectionBuilder(typeof(MyImmutableCollectionBuilder), nameof(MyImmutableCollectionBuilder.Build))] class MyImmutableCollection : IEnumerable<MyElement> { private readonly IReadOnlyList<MyElement> _list; internal MyImmutableCollection(List<MyElement> list) => _list = list.AsReadOnly(); // 可选:添加支持多参数的构造函数 public MyImmutableCollection(params (int code, string description)[] elements) : this(elements.Select(x => new MyElement(x.code, x.description)).ToList()) { } public MyElement this[int index] => _list[index]; public int Count => _list.Count; public IEnumerator<MyElement> GetEnumerator() => _list.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } // 编译期使用的Builder静态类 static class MyImmutableCollectionBuilder { public static MyImmutableCollection Build(ReadOnlySpan<MyElement> elements) { return new MyImmutableCollection(elements.ToList()); } }
使用效果:
var collection1 = new MyImmutableCollection([new MyElement()]); var collection2 = new MyImmutableCollection([new MyElement("ABC")]); var collection3 = new MyImmutableCollection([(1, "One"), (2, "Two")]);
该方案完全基于C#原生特性实现,无额外语法负担,集合本身无任何公开修改接口。
关于你提到的方法级init修饰符提案,目前确实已被撤回,暂无官方特性支持公开Add方法仅在初始化阶段可用,上述两种方案是当前生态下的最优解。
内容的提问来源于stack exchange,提问作者Dmitry Kotleev
相关产品推荐
相关产品推荐

