前置知识: C#、C#、C#

C# 泛型与集合

19 min中级

泛型类型系统、约束、协变逆变、List/Dictionary/HashSet/Queue/Stack/PriorityQueue、不可变集合、Frozen 集合、迭代器(yield)、LINQ to Objects、性能模型与生产实践

前置知识

学习目标

  • 掌握「历史动机与背景」的核心机制、典型用法与常见陷阱
  • 掌握「形式化定义」的核心机制、典型用法与常见陷阱
  • 掌握「理论推导」的核心机制、典型用法与常见陷阱
  • 掌握「代码示例」的核心机制、典型用法与常见陷阱
  • 掌握「对比分析」的核心机制、典型用法与常见陷阱

历史动机与背景

泛型的诞生:类型安全的复用

在泛型出现之前,复用容器代码只能依赖 System.Object 与装箱拆箱(boxing/unboxing)。.NET 1.x 的 ArrayList、Hashtable、Queue、Stack 接收 object,存在三类严重问题:

  1. 类型安全丧失:ArrayList 可同时装入 string、int、DateTime,运行时才会暴露类型错误。
  2. 性能损耗:值类型装入 ArrayList 需装箱为 object,读取时拆箱,伴随堆分配与类型检查。int 装箱在 32 位系统上从 4 字节膨胀为 12 字节(对象头 + 方法表指针 + 数据)。
  3. 代码可读性差:开发者需手动类型转换,编译期无法保证正确性。

2005 年,Don Syme 等人在 .NET 2.0 中引入泛型(Generics),同时由 C# 2.0、CLR 2.0 提供语言与运行时支持。设计目标:

  • 编译期类型检查:错误前置到编译阶段,避免运行时 InvalidCastException。
  • 零装箱开销:值类型实例化为专用代码,无 object 转换。
  • 代码复用:一份算法实现可适用于多种类型,保留类型信息。

CLR 泛型 vs Java 泛型

C# 泛型与 Java 泛型(2004 年 Java 5 引入)走出了两条截然不同的路径:

  • CLR 泛型(reified generics):类型参数在运行时存在,typeof(List<int>) 与 typeof(List<string>) 是不同的 Type 对象。值类型泛型由 CLR 为每种值类型生成专用代码(如 List<int> 与 List<double> 是两个不同的 JIT 编译产物);引用类型泛型共享同一份代码(List<string> 与 List<object> 共用方法实现,因引用都是 8 字节指针)。
  • Java 泛型(type erasure):类型参数仅存在于编译期,编译后被擦除为 Object。List<String> 与 List<Integer> 在运行时是同一个 List 类型。这导致无法 new T()、无法 instanceof List<String>、基本类型必须装箱(List<Integer> 而非 List<int>)。

CLR 选择 reified 的代价是元数据膨胀与潜在的代码膨胀,但获得了运行时类型信息与值类型零装箱的关键优势。

集合库的演进

  • .NET 1.0(2002):System.Collections 命名空间,非泛型 ArrayList、Hashtable、Queue、Stack、SortedList,基于 object。
  • .NET 2.0(2005):System.Collections.Generic,泛型 List<T>、Dictionary<TKey,TValue>、Queue<T>、Stack<T>、LinkedList<T>、SortedDictionary<TKey,TValue>、SortedList<TKey,TValue>、HashSet<T>(3.5 后)。
  • .NET 3.5(2007):HashSet<T>、Lookup<TKey,TElement>、扩展方法与 LINQ。
  • .NET 4.0(2010):System.Collections.Concurrent,ConcurrentDictionary<TKey,TValue>、ConcurrentQueue<T>、ConcurrentBag<T>、BlockingCollection<T>。
  • .NET 4.5(2012):IReadOnlyList<T>、IReadOnlyDictionary<TKey,TValue>、IReadOnlyCollection<T>。
  • .NET 4.6(2015):Array.Empty<T>()、Span<T> 萌芽。
  • .NET Core 2.0(2017):Span<T>、Memory<T>、ReadOnlySpan<T>。
  • .NET Core 3.0(2019):Stack<T> 内部优化为基于数组的紧凑布局。
  • .NET 5(2020):集合性能大幅优化,List<T>.AddRange 改进、Dictionary 内部结构升级。
  • .NET 6(2021):PriorityQueue<TElement,TPriority> 引入,基于最小堆。
  • .NET 8(2023):FrozenDictionary<TKey,TValue>、FrozenSet<T>,针对只读场景优化查询性能;CollectionView、SortedDictionary 改进。
  • .NET 9(2024):OrderedDictionary<TKey,TValue>(泛型版)、ReadOnlySet<T>、集合表达式([] 与 ..)增强。

C# 12(2023)引入集合表达式(Collection Expressions),统一了数组、List<T>、Span<T>、IEnumerable<T> 的初始化语法,消除了历史遗留的多套初始化风格。

形式化定义

泛型类型的形式化模型

泛型类型可形式化为类型构造器(type constructor)。设类型宇宙 U\mathcal{U},泛型类型 GG 是从类型参数到具体类型的函数:

G:Un→UG : \mathcal{U}^n \to \mathcal{U}

其中 nn 是类型参数个数。例如 List<T> 是 n=1n=1 的类型构造器,Dictionary<TKey, TValue> 是 n=2n=2。

实例化:给定类型实参 T1,T2,…,Tn∈UT_1, T_2, \ldots, T_n \in \mathcal{U},实例化类型为 G⟨T1,…,Tn⟩∈UG\langle T_1, \ldots, T_n \rangle \in \mathcal{U}。CLR 在运行时为每个不同的实例化创建独立的 Type 对象:

G⟨T1,…,Tn⟩≡G⟨U1,…,Un⟩  ⟺  ∀i:Ti≡UiG\langle T_1, \ldots, T_n \rangle \equiv G\langle U_1, \ldots, U_n \rangle \iff \forall i : T_i \equiv U_i

子类型化与协变逆变

设 FF 是类型构造器,S⪯TS \preceq T 表示子类型关系。

  • 协变(Covariant):FF 协变当且仅当 S⪯T⇒F⟨S⟩⪯F⟨T⟩S \preceq T \Rightarrow F\langle S \rangle \preceq F\langle T \rangle
  • 逆变(Contravariant):FF 逆变当且仅当 S⪯T⇒F⟨T⟩⪯F⟨S⟩S \preceq T \Rightarrow F\langle T \rangle \preceq F\langle S \rangle
  • 不变(Invariant):既不协变也不逆变

C# 使用 out 标记协变类型参数(仅出现在输出位置),in 标记逆变类型参数(仅出现在输入位置):

out T⇒T 仅作为返回类型,in T⇒T 仅作为参数类型\text{out } T \Rightarrow T \text{ 仅作为返回类型}, \quad \text{in } T \Rightarrow T \text{ 仅作为参数类型}

类型安全证明(协变):设 IProducer<out T> 有方法 T Produce()。若 StringProducer : IProducer<string>,则 string Produce() 满足。将 IProducer<string> 视为 IProducer<object> 调用 Produce() 返回 string,可安全转为 object。类型安全成立。

类型安全反例(若 List<T> 协变):若 List<string> 可视为 List<object>,则 list.Add(42) 会向 string 数组写入 int,运行时崩溃。故 List<T> 必须不变。

集合的代数结构

集合类型可视为代数结构 (S,⊕,⊖,∩,∪)(S, \oplus, \ominus, \cap, \cup),其中 SS 是元素集合,运算定义为:

  • 插入 ⊕:S×E→S\oplus : S \times E \to S
  • 删除 ⊖:S×E→S\ominus : S \times E \to S
  • 并集 ∪:S×S→S\cup : S \times S \to S
  • 交集 ∩:S×S→S\cap : S \times S \to S
  • 差集 ∖:S×S→S\setminus : S \times S \to S

不同集合类型对运算的实现复杂度差异显著:

运算List<T>HashSet<T>SortedSet<T>Dictionary
插入均摊 O(1)O(1)均摊 O(1)O(1)O(log⁡n)O(\log n)均摊 O(1)O(1)
删除O(n)O(n)O(1)O(1)O(log⁡n)O(\log n)O(1)O(1)
查找O(n)O(n)O(1)O(1)O(log⁡n)O(\log n)O(1)O(1)
排序O(nlog⁡n)O(n \log n)---
集合运算O(n⋅m)O(n \cdot m)O(n+m)O(n + m)O(n+m)O(n + m)-

哈希函数的形式化

Dictionary<TKey, TValue> 与 HashSet<T> 基于哈希表。哈希函数 h:K→Zmh : K \to \mathbb{Z}_m,其中 mm 是桶数。理想哈希函数满足:

  1. 均匀分布:∀i,P(h(k)=i)≈1/m\forall i, P(h(k) = i) \approx 1/m
  2. 雪崩效应:输入微小变化导致输出大幅变化
  3. 确定性:相同输入恒定输出

冲突解决:CLR 采用链地址法(separate chaining),每个桶维护一个链表。装载因子 α=n/m\alpha = n/m,查找复杂度为 O(1+α)O(1 + \alpha)。当 α\alpha 超过阈值(默认 0.72 或 1.0 视集合而定)时触发扩容:m→2m+1m \to 2m + 1(质数),重新哈希所有元素,均摊 O(1)O(1)。

理论推导

List<T> 动态扩容的均摊分析

List<T> 内部维护数组 _items,初始容量为 0 或构造时指定。Add 操作:

  1. 若 Count < Capacity,直接写入 _items[Count++],O(1)O(1)。
  2. 若 Count == Capacity,扩容:Array.Resize 分配新数组(容量翻倍 + 1),复制旧元素,再写入。扩容代价 O(n)O(n)。

均摊复杂度:设扩容序列为 c0=0,c1=1,c2=2,c3=4,…,ck=2k−1c_0 = 0, c_1 = 1, c_2 = 2, c_3 = 4, \ldots, c_k = 2^{k-1}。前 nn 次插入的总代价:

T(n)=∑i=1n1+∑j=1log⁡2n2j=n+(2n−1)=O(n)T(n) = \sum_{i=1}^{n} 1 + \sum_{j=1}^{\log_2 n} 2^j = n + (2n - 1) = O(n)

均摊每次插入:T(n)/n=O(1)T(n) / n = O(1)。

容量预分配优化:若已知最终元素数 nn,构造时使用 new List<T>(n) 或调用 EnsureCapacity(n),避免 O(log⁡n)O(\log n) 次扩容与复制,总代价降至 O(n)O(n)(系数减半)。

Dictionary 哈希冲突的概率分析

设哈希表桶数 mm,元素数 nn,哈希函数均匀分布。生日问题给出至少一对冲突的概率:

P(collision)=1−m!mn(m−n)!≈1−e−n(n−1)/(2m)P(\text{collision}) = 1 - \frac{m!}{m^n (m-n)!} \approx 1 - e^{-n(n-1)/(2m)}

当 n≈mn \approx \sqrt{m} 时,冲突概率约 50%。链地址法下,单桶链表长度的期望为 α=n/m\alpha = n/m,查找复杂度 O(1+α)O(1 + \alpha)。

.NET 实现:Dictionary 在装载因子超过 0.72(默认)时扩容至下一个质数。质数选择避免哈希值与桶数的公约数导致的聚集(clustering)。CLR 维护质数表 HashHelpers.primes,扩容时查找下一个质数。

哈希碰撞攻击:若攻击者能构造大量哈希相同的键,将 Dictionary 退化为 O(n)O(n) 查找的链表,引发 DoS。.NET 4.5+ 引入随机化字符串哈希(UseRandomizedStringHashAlgorithm),每次应用启动使用不同种子。

SortedDictionary 的红黑树复杂度

SortedDictionary<TKey, TValue> 基于红黑树(Red-Black Tree),保证:

  • 树高 h≤2log⁡2(n+1)h \leq 2 \log_2(n+1)
  • 插入、删除、查找均为 O(log⁡n)O(\log n)
  • 有序遍历 O(n)O(n)

红黑树通过五条不变量维护平衡:

  1. 节点是红色或黑色
  2. 根节点是黑色
  3. 叶子(NIL)是黑色
  4. 红色节点的子节点必须是黑色(不能连续两个红)
  5. 从任一节点到其所有叶子节点的路径包含相同数量的黑色节点

这些约束保证最长路径不超过最短路径的两倍,确保 O(log⁡n)O(\log n) 高度。

协变逆变的类型推断

C# 编译器在方法调用时进行类型推断。设方法签名:

TResult Select<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, TResult> selector)

调用 Select(list, x => x.ToString()) 时,编译器推断:

  1. list 类型为 List<int>,List<int> 实现 IEnumerable<int>,推断 TSource = int
  2. Lambda x => x.ToString() 中 x : int,返回 string,推断 TResult = string
  3. 实例化为 Select<int, string>

类型推断算法基于约束求解,复杂度 O(n)O(n)(nn 为类型参数数)。

Span<T> 与 Memory<T> 的内存零拷贝

Span<T> 是对连续内存区域的类型安全视图,定义为:

Span⟨T⟩=(ref T,int length)\text{Span}\langle T \rangle = (\text{ref } T, \text{int length})
  • ref T 是托管指针,指向内存起始
  • length 是元素数

Span<T> 可覆盖托管数组、栈分配 stackalloc、非托管内存 NativeMemory,无运行时装箱。但其必须 ref struct(不能装箱、不能作为字段、不能跨 await),保证生命周期局限于栈帧。

Memory<T> 是 Span<T> 的堆可存储版本,可用于异步方法字段,通过 MemoryMarshal.TryGetArray 获取底层 ArraySegment<T>。

切片 span.Slice(start, length) 仅需 O(1)O(1) 修改指针与长度,无需复制数据,是零拷贝算法的关键。

代码示例

示例 1:泛型类与多约束

using System;
using System.Collections.Generic;

/// <summary>
/// 泛型缓存:支持带 TTL 的键值存储。
/// 多约束:TKey 必须 implements IComparable<TKey> 且有无参构造,
/// TValue 必须是 class(引用类型)。
/// </summary>
public class TtlCache<TKey, TValue>
    where TKey : notnull, IComparable<TKey>, IEquatable<TKey>
    where TValue : class
{
    // 内部条目:存储值与过期时间
    private readonly Dictionary<TKey, Entry> _store = new();
    private readonly TimeSpan _ttl;
    private readonly object _lock = new();

    public TtlCache(TimeSpan ttl) => _ttl = ttl;

    /// <summary>设置键值对,过期时间为当前时间 + TTL。</summary>
    public void Set(TKey key, TValue value)
    {
        lock (_lock)
        {
            _store[key] = new Entry(value, DateTime.UtcNow + _ttl);
        }
    }

    /// <summary>
    /// 尝试获取值。若已过期则移除并返回 false。
    /// 使用 ref 局部变量避免两次哈希查找。
    /// </summary>
    public bool TryGet(TKey key, out TValue? value)
    {
        lock (_lock)
        {
            ref var entry = ref CollectionsMarshal.GetValueRefOrNullRef(_store, key);
            if (!Unsafe.IsNullRef(ref entry))
            {
                if (entry.ExpiresAt > DateTime.UtcNow)
                {
                    value = entry.Value;
                    return true;
                }
                _store.Remove(key);
            }
        }
        value = default;
        return false;
    }

    /// <summary>清理所有过期条目。</summary>
    public int Cleanup()
    {
        lock (_lock)
        {
            var now = DateTime.UtcNow;
            var expired = new List<TKey>();
            foreach (var kv in _store)
            {
                if (kv.Value.ExpiresAt <= now)
                    expired.Add(kv.Key);
            }
            expired.ForEach(k => _store.Remove(k));
            return expired.Count;
        }
    }

    private readonly record struct Entry(TValue Value, DateTime ExpiresAt);
}

示例 2:协变逆变与自定义集合

using System;
using System.Collections;
using System.Collections.Generic;

/// <summary>协变生产者接口:仅输出 T,不消费 T。</summary>
public interface IProducer<out T>
{
    T Produce();
    IEnumerable<T> ProduceMany(int count);
}

/// <summary>逆变消费者接口:仅消费 T,不输出 T。</summary>
public interface IConsumer<in T>
{
    void Consume(T item);
    void ConsumeAll(IEnumerable<T> items);
}

/// <summary>具体生产者:字符串工厂。</summary>
public sealed class StringFactory : IProducer<string>
{
    private int _counter = 0;

    public string Produce() => $"item-{++_counter}";

    public IEnumerable<string> ProduceMany(int count)
    {
        for (int i = 0; i < count; i++)
            yield return Produce();
    }
}

/// <summary>具体消费者:日志记录器,能消费任意 object。</summary>
public sealed class Logger : IConsumer<object>
{
    public void Consume(object item) =>
        Console.WriteLine($"[Log] {item}");

    public void ConsumeAll(IEnumerable<object> items)
    {
        foreach (var item in items)
            Consume(item);
    }
}

public class CovarianceDemo
{
    public static void Run()
    {
        IProducer<string> stringProducer = new StringFactory();
        // 协变:IProducer<string> 可视为 IProducer<object>
        IProducer<object> objectProducer = stringProducer;
        object val = objectProducer.Produce(); // 返回 string,安全转为 object

        IConsumer<object> objectConsumer = new Logger();
        // 逆变:IConsumer<object> 可视为 IConsumer<string>
        IConsumer<string> stringConsumer = objectConsumer;
        stringConsumer.Consume("hello"); // 调用接收 object 的方法,string 是 object
    }
}

示例 3:自定义可枚举集合(环形缓冲区)

using System;
using System.Collections;
using System.Collections.Generic;

/// <summary>
/// 环形缓冲区:固定容量的 FIFO 队列,满时覆盖最旧元素。
/// 实现 IEnumerable<T> 支持 LINQ 与 foreach。
/// </summary>
public sealed class CircularBuffer<T> : IEnumerable<T>, IReadOnlyCollection<T>
{
    private readonly T[] _buffer;
    private int _head; // 下一个写入位置
    private int _count;

    public CircularBuffer(int capacity)
    {
        if (capacity <= 0)
            throw new ArgumentOutOfRangeException(nameof(capacity));
        _buffer = new T[capacity];
        _head = 0;
        _count = 0;
    }

    public int Capacity => _buffer.Length;
    public int Count => _count;

    /// <summary>添加元素。若已满,覆盖最旧元素并返回被覆盖的值。</summary>
    public bool TryAdd(T item, out T? overwritten)
    {
        overwritten = default;
        bool wasFull = _count == _buffer.Length;

        if (wasFull)
            overwritten = _buffer[_head]; // 即将被覆盖

        _buffer[_head] = item;
        _head = (_head + 1) % _buffer.Length;
        if (!wasFull) _count++;

        return wasFull;
    }

    /// <summary>按写入顺序返回元素(最旧到最新)。</summary>
    public IEnumerator<T> GetEnumerator()
    {
        int start = _count == _buffer.Length ? _head : 0;
        for (int i = 0; i < _count; i++)
        {
            yield return _buffer[(start + i) % _buffer.Length];
        }
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class CircularBufferDemo
{
    public static void Run()
    {
        var buf = new CircularBuffer<int>(3);
        for (int i = 1; i <= 5; i++)
        {
            buf.TryAdd(i, out var overwritten);
            if (overwritten != default)
                Console.WriteLine($"覆盖: {overwritten}");
        }
        // 输出: 覆盖: 1, 覆盖: 2
        Console.WriteLine(string.Join(", ", buf)); // 3, 4, 5
        Console.WriteLine($"Sum: {buf.Sum()}, Max: {buf.Max()}"); // 12, 5
    }
}

示例 4:迭代器组合与惰性求值

using System;
using System.Collections.Generic;

/// <summary>惰性序列生成器:基于 yield 的函数式风格。</summary>
public static class Seq
{
    /// <summary>无限自然数序列(从 1 开始)。</summary>
    public static IEnumerable<int> Naturals()
    {
        int n = 1;
        while (true) yield return n++;
    }

    /// <summary>斐波那契数列。</summary>
    public static IEnumerable<long> Fibonacci()
    {
        long a = 0, b = 1;
        while (true)
        {
            yield return b;
            (a, b) = (b, a + b);
        }
    }

    /// <summary>素数序列(埃拉托色尼筛法变种)。</summary>
    public static IEnumerable<int> Primes()
    {
        yield return 2;
        var primes = new List<int> { 2 };
        int candidate = 3;
        while (true)
        {
            bool isPrime = true;
            int sqrt = (int)Math.Sqrt(candidate);
            foreach (var p in primes)
            {
                if (p > sqrt) break;
                if (candidate % p == 0) { isPrime = false; break; }
            }
            if (isPrime)
            {
                primes.Add(candidate);
                yield return candidate;
            }
            candidate += 2;
        }
    }

    /// <summary>通用过滤:惰性迭代器组合的核心。</summary>
    public static IEnumerable<T> Where<T>(
        this IEnumerable<T> source, Func<T, bool> predicate)
    {
        foreach (var item in source)
            if (predicate(item))
                yield return item;
    }

    /// <summary>通用映射。</summary>
    public static IEnumerable<TResult> Select<T, TResult>(
        this IEnumerable<T> source, Func<T, TResult> selector)
    {
        foreach (var item in source)
            yield return selector(item);
    }

    /// <summary>取前 n 个。</summary>
    public static IEnumerable<T> Take<T>(
        this IEnumerable<T> source, int count)
    {
        int i = 0;
        foreach (var item in source)
        {
            if (i++ >= count) yield break;
            yield return item;
        }
    }
}

public class IteratorDemo
{
    public static void Run()
    {
        // 前 10 个偶斐波那契数的平方
        var result = Seq.Fibonacci()
            .Where(n => n % 2 == 0)
            .Select(n => n * n)
            .Take(10)
            .ToList();

        Console.WriteLine(string.Join(", ", result));
        // 1, 9, 64, 1764, 16641, 93025, 113569, 4356, 116964, 278784
        // (具体值取决于是否从 1 开始)
    }
}

示例 5:Dictionary 性能优化与并发场景

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

public class DictionaryPerformance
{
    /// <summary>统计词频:使用 GetValueRefOrNullRef 优化重复查找。</summary>
    public static Dictionary<string, int> CountWords(IEnumerable<string> words)
    {
        var counts = new Dictionary<string, int>();
        foreach (var word in words)
        {
            // .NET 6+: 获取引用避免两次哈希查找
            ref int count = ref CollectionsMarshal.GetValueRefOrAddDefault(counts, word, out _);
            count++;
        }
        return counts;
    }

    /// <summary>并发词频统计:使用 ConcurrentDictionary + 多线程。</summary>
    public static ConcurrentDictionary<string, int> CountWordsParallel(IEnumerable<string> words)
    {
        var counts = new ConcurrentDictionary<string, int>();
        var batches = words.Chunk(10_000);
        Parallel.ForEach(batches, batch =>
        {
            foreach (var word in batch)
                counts.AddOrUpdate(word, 1, (_, old) => old + 1);
        });
        return counts;
    }

    /// <summary>对比不同字典在只读场景下的查询性能。</summary>
    public static void BenchmarkReadOnlyLookup()
    {
        var keys = Enumerable.Range(0, 100_000).Select(i => i.ToString()).ToArray();
        var dict = keys.ToDictionary(k => k, k => k);
        var frozen = dict.ToFrozenDictionary();
        var immutable = dict.ToImmutableDictionary();

        // 预热
        for (int i = 0; i < 1000; i++)
        {
            _ = dict[keys[i]];
            _ = frozen[keys[i]];
            _ = immutable[keys[i]];
        }

        var sw = Stopwatch.StartNew();
        for (int i = 0; i < keys.Length; i++) _ = dict[keys[i]];
        sw.Stop();
        Console.WriteLine($"Dictionary: {sw.ElapsedMilliseconds} ms");

        sw.Restart();
        for (int i = 0; i < keys.Length; i++) _ = frozen[keys[i]];
        sw.Stop();
        Console.WriteLine($"FrozenDictionary: {sw.ElapsedMilliseconds} ms");

        sw.Restart();
        for (int i = 0; i < keys.Length; i++) _ = immutable[keys[i]];
        sw.Stop();
        Console.WriteLine($"ImmutableDictionary: {sw.ElapsedMilliseconds} ms");
    }
}

示例 6:不可变集合与函数式更新

using System;
using System.Collections.Generic;
using System.Collections.Immutable;

/// <summary>不可变配置树:演示 ImmutableDictionary 的函数式更新。</summary>
public sealed class AppConfig
{
    public ImmutableDictionary<string, string> Settings { get; }
    public ImmutableList<string> AllowedOrigins { get; }
    public ImmutableDictionary<string, int> RateLimits { get; }

    public AppConfig()
        : this(ImmutableDictionary<string, string>.Empty,
               ImmutableList<string>.Empty,
               ImmutableDictionary<string, int>.Empty)
    { }

    private AppConfig(
        ImmutableDictionary<string, string> settings,
        ImmutableList<string> origins,
        ImmutableDictionary<string, int> limits)
    {
        Settings = settings;
        AllowedOrigins = origins;
        RateLimits = limits;
    }

    /// <summary>更新设置:返回新的 AppConfig,原对象不变。</summary>
    public AppConfig WithSetting(string key, string value) =>
        new(Settings.SetItem(key, value), AllowedOrigins, RateLimits);

    /// <summary>添加允许的源。</summary>
    public AppConfig WithOrigin(string origin) =>
        new(Settings, AllowedOrigins.Add(origin), RateLimits);

    /// <summary>更新速率限制。</summary>
    public AppConfig WithRateLimit(string endpoint, int limit) =>
        new(Settings, AllowedOrigins, RateLimits.SetItem(endpoint, limit));

    /// <summary>快照:所有集合已经是不可变的,可直接返回。</summary>
    public AppConfig Snapshot() => this;
}

public class ImmutableDemo
{
    public static void Run()
    {
        var config = new AppConfig()
            .WithSetting("app.name", "FANDEX")
            .WithSetting("env", "production")
            .WithOrigin("https://fandex.example.com")
            .WithRateLimit("/api/search", 100);

        var updated = config.WithSetting("env", "staging");
        Console.WriteLine(config.Settings["env"]);       // production(原对象未变)
        Console.WriteLine(updated.Settings["env"]);      // staging
    }
}

示例 7:PriorityQueue 实现任务调度

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

/// <summary>
/// 优先级任务调度器:使用 PriorityQueue 按优先级出队。
/// 演示 .NET 6+ PriorityQueue 的典型用法。
/// </summary>
public sealed class PriorityScheduler : IDisposable
{
    private readonly PriorityQueue<Func<Task>, (int Priority, long Sequence)> _queue = new();
    private readonly object _lock = new();
    private readonly SemaphoreSlim _signal = new(0);
    private readonly CancellationTokenSource _cts = new();
    private long _sequence;
    private readonly Task _worker;

    public PriorityScheduler()
    {
        _worker = Task.Run(WorkerLoop);
    }

    /// <summary>提交任务:优先级数值越小越优先执行。</summary>
    public void Submit(Func<Task> task, int priority)
    {
        lock (_lock)
        {
            // 使用 (Priority, Sequence) 作为复合优先级,保证相同优先级 FIFO
            _queue.Enqueue(task, (priority, ++_sequence));
        }
        _signal.Release();
    }

    private async Task WorkerLoop()
    {
        await _signal.WaitAsync(_cts.Token);
        while (!_cts.IsCancellationRequested)
        {
            Func<Task> task;
            lock (_lock)
            {
                if (_queue.Count == 0)
                {
                    Monitor.Exit(_lock);
                    await _signal.WaitAsync(_cts.Token);
                    Monitor.Enter(_lock);
                    if (_queue.Count == 0) continue;
                }
                task = _queue.Dequeue();
            }

            try
            {
                await task();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"任务执行失败: {ex.Message}");
            }
        }
    }

    public void Dispose()
    {
        _cts.Cancel();
        _signal.Release();
        _worker.Wait(TimeSpan.FromSeconds(5));
        _cts.Dispose();
        _signal.Dispose();
    }
}

public class SchedulerDemo
{
    public static async Task Run()
    {
        using var scheduler = new PriorityScheduler();

        scheduler.Submit(async () =>
        {
            Console.WriteLine("低优先级任务");
            await Task.Delay(100);
        }, priority: 10);

        scheduler.Submit(async () =>
        {
            Console.WriteLine("高优先级任务");
            await Task.Delay(100);
        }, priority: 1);

        scheduler.Submit(async () =>
        {
            Console.WriteLine("中优先级任务");
            await Task.Delay(100);
        }, priority: 5);

        await Task.Delay(1000);
        // 输出顺序:高 -> 中 -> 低
    }
}

示例 8:自定义相等比较器与排序

using System;
using System.Collections.Generic;
using System.Linq;

/// <summary>不区分大小写的字符串比较器。</summary>
public sealed class CaseInsensitiveComparer : IEqualityComparer<string>, IComparer<string>
{
    public static readonly CaseInsensitiveComparer Instance = new();

    public bool Equals(string? x, string? y) =>
        string.Equals(x, y, StringComparison.OrdinalIgnoreCase);

    public int GetHashCode(string obj) =>
        StringComparer.OrdinalIgnoreCase.GetHashCode(obj);

    public int Compare(string? x, string? y) =>
        string.Compare(x, y, StringComparison.OrdinalIgnoreCase);
}

/// <summary>多字段排序器:链式比较。</summary>
public sealed class ChainedComparer<T> : IComparer<T>
{
    private readonly List<(Func<T, T, int> Compare, bool Descending)> _comparisons = new();

    public static ChainedComparer<T> Create() => new();

    public ChainedComparer<T> ThenBy<TKey>(Func<T, TKey> keySelector, bool descending = false)
        where TKey : IComparable<TKey>
    {
        _comparisons.Add((a, b =>
        {
            int r = keySelector(a).CompareTo(keySelector(b));
            return descending ? -r : r;
        }, false));
        return this;
    }

    public int Compare(T? x, T? y)
    {
        if (x is null && y is null) return 0;
        if (x is null) return -1;
        if (y is null) return 1;

        foreach (var (cmp, _) in _comparisons)
        {
            int r = cmp(x, y);
            if (r != 0) return r;
        }
        return 0;
    }
}

public class Person
{
    public string Name { get; set; } = "";
    public int Age { get; set; }
    public string City { get; set; } = "";

    public override string ToString() => $"{Name} ({Age}, {City})";
}

public class ComparerDemo
{
    public static void Run()
    {
        var people = new List<Person>
        {
            new() { Name = "Alice", Age = 30, City = "Beijing" },
            new() { Name = "Bob", Age = 25, City = "Shanghai" },
            new() { Name = "alice", Age = 28, City = "Beijing" },
            new() { Name = "Charlie", Age = 30, City = "Shenzhen" },
        };

        // 使用不区分大小写的比较器去重(按 Name)
        var byName = new Dictionary<string, Person>(CaseInsensitiveComparer.Instance);
        foreach (var p in people)
            byName[p.Name] = p;
        Console.WriteLine($"按 Name 去重后剩余: {byName.Count}"); // 3(Alice 与 alice 合并)

        // 链式排序:先按 City,再按 Age 降序
        var sorted = people
            .OrderBy(p => p.City)
            .ThenByDescending(p => p.Age)
            .ToList();
        sorted.ForEach(Console.WriteLine);
    }
}

示例 9:Span<T> 零拷贝切片

using System;
using System.Linq;
using System.Text;

public class SpanDemo
{
    /// <summary>统计字节数组中等于目标值的元素个数,零分配。</summary>
    public static int Count(byte[] source, byte target)
    {
        var span = source.AsSpan();
        int count = 0;
        foreach (var b in span)
            if (b == target) count++;
        return count;
    }

    /// <summary>反转字符串中的字符顺序,无需中间字符串。</summary>
    public static string ReverseString(string input)
    {
        if (string.IsNullOrEmpty(input)) return input;
        // 借助 Span<char> 避免多次字符串分配
        Span<char> buffer = input.Length <= 256
            ? stackalloc char[input.Length]
            : new char[input.Length];
        input.AsSpan().CopyTo(buffer);
        buffer.Reverse();
        return new string(buffer);
    }

    /// <summary>解析整数行:基于 ReadOnlySpan 的零拷贝解析。</summary>
    public static int[] ParseNumbers(ReadOnlySpan<char> input, char separator = ',')
    {
        var result = new List<int>();
        while (!input.IsEmpty)
        {
            int idx = input.IndexOf(separator);
            ReadOnlySpan<char> token = idx < 0 ? input : input[..idx];
            if (!token.IsEmpty && int.TryParse(token, out int n))
                result.Add(n);
            if (idx < 0) break;
            input = input[(idx + 1)..];
        }
        return result.ToArray();
    }

    /// <summary>base64 编码:使用 Span 避免中间 byte[] 分配。</summary>
    public static string ToBase64(ReadOnlySpan<byte> data)
    {
        // 预分配精确大小,避免 Convert.ToBase64String 内部缓冲区扩容
        Span<char> chars = stackalloc char[4 * ((data.Length + 2) / 3)];
        if (Convert.TryToBase64Chars(data, chars, out int written))
            return new string(chars[..written]);
        throw new InvalidOperationException("base64 编码失败");
    }

    public static void Run()
    {
        Console.WriteLine(ReverseString("hello")); // olleh
        var nums = ParseNumbers("1, 22, 333, 4444");
        Console.WriteLine(string.Join(", ", nums)); // 1, 22, 333, 4444

        var bytes = Encoding.UTF8.GetBytes("hello world");
        Console.WriteLine(ToBase64(bytes)); // aGVsbG8gd29ybGQ=
    }
}

示例 10:集合表达式(C# 12)与展开运算符

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;

public class CollectionExpressionDemo
{
    public static void Run()
    {
        // 统一初始化语法
        int[] array = [1, 2, 3];
        List<string> list = ["a", "b", "c"];
        Span<int> span = [1, 2, 3];
        HashSet<string> set = ["x", "y", "z"];
        ImmutableArray<int> immutable = [1, 2, 3];

        // 展开运算符 ..
        int[] a = [1, 2, 3];
        int[] b = [4, 5, 6];
        int[] combined = [..a, ..b, 7, 8, 9];
        Console.WriteLine(string.Join(", ", combined)); // 1,2,3,4,5,6,7,8,9

        // 作为方法参数
        Process([1, 2, 3, 4, 5]);
        Process([..a, ..b]);
    }

    /// <summary>方法接受 ReadOnlySpan,调用方可传集合表达式。</summary>
    static void Process(ReadOnlySpan<int> values)
    {
        Console.WriteLine($"处理 {values.Length} 个值: {string.Join(", ", values.ToArray())}");
    }

    /// <summary>返回集合表达式:编译器自动选择合适的目标类型。</summary>
    static IEnumerable<int> GenerateNumbers()
    {
        return [1, 2, 3, 4, 5];
    }

    /// <summary>泛型方法中的集合表达式(需指定目标类型)。</summary>
    static List<T> AppendDefault<T>(List<T> source)
    {
        // 此处不能直接用 [..source, default],编译器需明确目标类型
        var result = new List<T>(source) { default! };
        return result;
    }
}

对比分析

C# 泛型与其他语言泛型对比

特性C#(CLR)Java(JVM)C++(模板)Rust
实现机制Reified(运行时类型存在)Type Erasure(编译期擦除)编译期代码生成单态化
值类型泛型零装箱,专用代码必须装箱(Integer)零开销零开销
new T()通过 where T : new() 约束不支持通过默认构造模板通过 Default trait
运行时类型信息typeof(T) 可用不可用不可用(无反射)受限(TypeId)
协变逆变out/in 显式声明通配符 ? extends/? super不支持生命周期与 trait bound
约束where T : IComparable<T><T extends Comparable<T>>概念(C++20)T: Ord
代码膨胀引用类型共享代码无(共享 Object)严重(每种类型独立生成)严重
反射完整支持受限不支持受限

讨论:C# 泛型在类型安全、性能、表达力之间取得了良好平衡。Java 通过类型擦除简化实现,但牺牲了运行时类型信息与值类型效率;C++ 模板是最强大的元编程工具,但缺乏约束导致错误信息难读、代码膨胀严重;Rust 通过 trait bound 与单态化实现了零开销抽象,但学习曲线陡峭。

集合类型选择决策矩阵

需求场景推荐集合备选原因
频繁随机索引访问List<T> / T[]ArrayO(1)O(1) 索引,缓存友好
频繁尾部增删List<T>Queue<T>均摊 O(1)O(1),但中间插入慢
频繁首尾增删LinkedList<T>DequeO(1)O(1) 双端操作,但内存开销大
键值查找Dictionary<TKey,TValue>SortedDictionaryO(1)O(1) 查找
有序键值查找SortedDictionarySortedListO(log⁡n)O(\log n) 查找,插入 O(log⁡n)O(\log n)
内存敏感的有序SortedList<TKey,TValue>-紧凑数组,但插入 O(n)O(n)
元素去重HashSet<T>Dictionary(值设为 byte)O(1)O(1) 包含检查
有序去重SortedSet<T>-O(log⁡n)O(\log n) 有序操作
先进先出Queue<T>ConcurrentQueueO(1)O(1) 入队出队
后进先出Stack<T>ConcurrentStackO(1)O(1) 入栈出栈
优先级出队PriorityQueue<TElement,TPriority>-O(log⁡n)O(\log n) 堆操作
只读快查FrozenDictionary (.NET 8+)ImmutableDictionary极速查询,构建代价高
只读快查(小集合)FrozenSetHashSet编译期优化,5-20 元素最快
多线程读写ConcurrentDictionaryDictionary + 锁细粒度锁,并发友好
生产者-消费者ConcurrentQueue / BlockingCollection-线程安全入队出队
不可变持久化ImmutableList<T>ImmutableArray函数式更新,结构共享
高性能零拷贝Span<T> / Memory<T>T[] + offset/length无分配切片

不可变集合对比

集合内部结构构建成本查询复杂度更新复杂度适用场景
ImmutableArray<T>数组O(n)O(n) 复制O(1)O(1) 索引O(n)O(n) 全复制构建后只读,频繁索引
ImmutableList<T>AVL 树O(log⁡n)O(\log n)O(log⁡n)O(\log n)O(log⁡n)O(\log n)频繁更新,结构共享
ImmutableDictionary哈希字典树O(log⁡n)O(\log n)O(log⁡n)O(\log n) 平均O(log⁡n)O(\log n)频繁更新键值
ImmutableHashSet哈希集合树O(log⁡n)O(\log n)O(log⁡n)O(\log n) 平均O(log⁡n)O(\log n)频繁更新去重
FrozenDictionary优化哈希表O(n)O(n) 高O(1)O(1) 极快不可更新启动后只读配置
FrozenSet优化哈希表O(n)O(n) 高O(1)O(1) 极快不可更新启动后只读集合
ReadOnlyDictionary包装现有字典O(1)O(1) 包装与底层一致不可更新视图层防修改

哈希集合与排序集合复杂度对比

操作HashSet<T>SortedSet<T>List<T>
Add均摊 O(1)O(1)O(log⁡n)O(\log n)均摊 O(1)O(1)
RemoveO(1)O(1)O(log⁡n)O(\log n)O(n)O(n)
ContainsO(1)O(1)O(log⁡n)O(\log n)O(n)O(n)
ClearO(n)O(n)O(n)O(n)O(n)O(n)
有序遍历无序O(n)O(n) 升序O(n)O(n) 输入序
范围查询不支持GetViewBetween O(log⁡n+k)O(\log n + k)Skip/Take O(n)O(n)
集合运算O(n)O(n)O(nlog⁡n)O(n \log n)O(n2)O(n^2)

常见陷阱与反模式

陷阱 1:在 foreach 中修改集合

// 反模式:迭代时修改集合,抛出 InvalidOperationException
var list = new List<int> { 1, 2, 3, 4, 5 };
foreach (var n in list)
{
    if (n % 2 == 0)
        list.Remove(n); // 抛出异常:集合已修改
}

// 正确:使用 RemoveAll 一次性删除
list.RemoveAll(n => n % 2 == 0);

// 或:先收集要删除的,再倒序删除
var toRemove = list.Where(n => n % 2 == 0).ToList();
foreach (var n in toRemove)
    list.Remove(n);

// 或:迭代时使用 for 循环倒序
for (int i = list.Count - 1; i >= 0; i--)
{
    if (list[i] % 2 == 0)
        list.RemoveAt(i);
}

原因:List<T>.Enumerator 维护版本号,Add/Remove/Clear 递增版本号,迭代时检查不一致则抛异常。

陷阱 2:Dictionary 键的可变性

// 反模式:使用可变对象作为 Dictionary 键,修改后丢失
var dict = new Dictionary<Person, string>();
var p = new Person { Name = "Alice", Age = 30 };
dict[p] = "engineer";
p.Age = 31; // 修改了影响哈希码的字段
_ = dict[p]; // KeyNotFoundException,哈希码已变,找不到桶

// 正确:使用不可变对象作为键
public sealed record PersonKey(string Name, int Age);
var key = new PersonKey("Alice", 30);
dict[key] = "engineer";
// PersonKey 是 record,无法修改,键永远稳定

原则:作为字典键的对象必须不可变,或至少影响 GetHashCode 与 Equals 的字段不可变。

陷阱 3:值类型集合的装箱

// 反模式:非泛型 ArrayList 装箱值类型,性能极差
var list = new ArrayList();
for (int i = 0; i < 1_000_000; i++)
    list.Add(i); // 每次装箱:4 字节 int -> 12 字节 boxed object
// 总内存:12 MB 而非 4 MB,且装箱耗时

// 正确:使用泛型 List<int>
var list = new List<int>();
for (int i = 0; i < 1_000_000; i++)
    list.Add(i); // 直接写入 int[],无装箱

性能差距:在 .NET 8 实测,相同操作 List<int> 比 ArrayList 快约 5-10 倍,内存占用少 3 倍。

陷阱 4:误解协变只读安全

// 误以为 IEnumerable<object> 可以写入
IEnumerable<object> objects = new List<string> { "a", "b" };
// objects.Add("c"); // 编译错误:IEnumerable<object> 没有 Add
// 但若强制转换为 List<object>:
var list = (List<object>)objects; // InvalidCastException

// 正确:使用 IEnumerable<T> 仅读取
foreach (object o in objects)
    Console.WriteLine(o);

// 需要写入:创建新集合
var writable = objects.ToList(); // List<object>
writable.Add("c");

陷阱 5:迭代器的延迟执行副作用

// 反模式:多次枚举 yield 迭代器触发重复计算
IEnumerable<int> Generate()
{
    Console.WriteLine("generating...");
    for (int i = 0; i < 5; i++) yield return i;
}

var nums = Generate();
var sum = nums.Sum();      // 输出 "generating...",计算
var count = nums.Count();  // 再次输出 "generating...",重新计算
var list = nums.ToList();  // 第三次输出 "generating..."

// 正确:缓存结果
var cached = nums.ToList(); // 仅迭代一次
var sum = cached.Sum();
var count = cached.Count;

原则:IEnumerable<T> 可能是延迟序列,多次遍历会重复执行。需要多次访问时先 ToList() 或 ToArray() 物化。

陷阱 6:线程安全的误解

// 反模式:多个线程同时向 List<T> 添加,数据竞争
var list = new List<int>();
Parallel.For(0, 10000, i => list.Add(i)); // 可能丢失元素、抛异常、数据损坏

// 正确:使用 ConcurrentBag 或加锁
var bag = new ConcurrentBag<int>();
Parallel.For(0, 10000, i => bag.Add(i));

// 或:使用锁
var list2 = new List<int>();
var lockObj = new object();
Parallel.For(0, 10000, i =>
{
    lock (lockObj) list2.Add(i);
});

// 或:先分区后合并
var results = Enumerable.Range(0, 10000)
    .AsParallel()
    .Select(i => i)
    .ToList(); // PLINQ 自动处理

陷阱 7:HashSet 性能因哈希函数退化

// 反模式:自定义类型未重写 GetHashCode/Equals,使用默认引用相等
public class Item
{
    public int Id { get; set; }
    // 未重写 Equals 与 GetHashCode
}

var set = new HashSet<Item>();
set.Add(new Item { Id = 1 });
set.Add(new Item { Id = 1 }); // 不同对象,被加入,去重失败
Console.WriteLine(set.Count); // 2,预期 1

// 正确:重写 Equals 与 GetHashCode,或使用 record
public sealed record Item(int Id);

var set2 = new HashSet<Item>();
set2.Add(new Item(1));
set2.Add(new Item(1)); // Equals 返回 true,被去重
Console.WriteLine(set2.Count); // 1

原则:放入 HashSet<T> 或作为 Dictionary 键的类型必须正确实现 Equals 与 GetHashCode,保证相等对象有相同哈希码。record 自动实现结构化相等。

陷阱 8:List 容量未预分配导致扩容开销

// 反模式:知道最终大小但未预分配,触发多次扩容
var list = new List<int>();
for (int i = 0; i < 1_000_000; i++)
    list.Add(i); // 触发约 20 次扩容与复制

// 正确:构造时指定容量
var list2 = new List<int>(1_000_000);
for (int i = 0; i < 1_000_000; i++)
    list2.Add(i); // 无扩容

// 或:使用 EnsureCapacity
var list3 = new List<int>();
list3.EnsureCapacity(1_000_000);
for (int i = 0; i < 1_000_000; i++)
    list3.Add(i);

性能差距:1 百万元素插入,预分配版本快约 2 倍,内存碎片更少。

陷阱 9:误用 LINQ 多次枚举数据库查询

// 反模式:EF Core 查询作为 IEnumerable 多次遍历,每次访问数据库
using var db = new AppDbContext();
var query = db.Users.Where(u => u.Age > 18); // IQueryable<User>

// 第一次:执行 SQL
var first = query.FirstOrDefault();
// 第二次:再次执行 SQL
var count = query.Count();
// 第三次:再次执行 SQL
var all = query.ToList();

// 正确:一次性加载到内存
var users = db.Users.Where(u => u.Age > 18).ToList();
var first = users.FirstOrDefault();
var count = users.Count;

// 或:根据需要分别构造不同 SQL
var first = db.Users.Where(u => u.Age > 18).FirstOrDefault();
var count = db.Users.Where(u => u.Age > 18).Count();

陷阱 10:Span<T> 错误的栈分配

// 反模式:stackalloc 大量数据导致栈溢出
public static string Process(string input)
{
    Span<char> buffer = stackalloc char[input.Length]; // 输入 100MB 时栈溢出
    // ...
    return new string(buffer);
}

// 正确:根据大小选择栈分配或堆分配
public static string Process(string input)
{
    if (input.Length > 256)
    {
        Span<char> buffer = new char[input.Length]; // 堆分配
        input.AsSpan().CopyTo(buffer);
        // ... 处理
        return new string(buffer);
    }
    else
    {
        Span<char> buffer = stackalloc char[input.Length]; // 栈分配
        input.AsSpan().CopyTo(buffer);
        // ... 处理
        return new string(buffer);
    }
}

// 反模式:将 Span 存储为字段
public class Bad
{
    private Span<int> _data; // 编译错误:Span<T> 是 ref struct,不能作为字段
}

// 反模式:跨 await 使用 Span
public async Task BadAsync()
{
    Span<byte> buffer = stackalloc byte[1024];
    await SomeAsyncMethod(); // 编译错误:Span 不能跨 await
}

工程实践

实践 1:选择正确的集合类型

决策流程:

  1. 是否需要键值映射?
    • 是 → 是否需要有序?是 → SortedDictionary;否 → Dictionary
    • 否 → 继续
  2. 是否需要去重?
    • 是 → 是否需要有序?是 → SortedSet;否 → HashSet
    • 否 → 继续
  3. 是否需要随机索引访问?
    • 是 → List<T> 或 T[]
    • 否 → 继续
  4. 访问模式?
    • FIFO → Queue<T>
    • LIFO → Stack<T>
    • 优先级 → PriorityQueue<TElement, TPriority>
    • 双端频繁增删 → LinkedList<T>
  5. 是否线程安全?
    • 是 → ConcurrentDictionary / ConcurrentQueue / ConcurrentBag
    • 否 → 上述选择
  6. 是否只读且长期使用?
    • 是 → FrozenDictionary / FrozenSet(启动时构建)
    • 否 → 上述选择

实践 2:性能优化的 7 个技巧

// 1. 预分配容量
var list = new List<int>(knownSize);
var dict = new Dictionary<string, int>(expectedCount);

// 2. 使用 CollectionsMarshal 避免重复哈希查找
ref int value = ref CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out _);
value++;

// 3. 只读查询使用 AsReadOnly 或 FrozenSet
var readOnly = list.AsReadOnly();
var frozen = dict.ToFrozenDictionary();

// 4. 大集合分批处理使用 Chunk
foreach (var batch in largeList.Chunk(1000))
    ProcessBatch(batch);

// 5. 使用 Array.Empty<T>() 代替 new T[0]
static IEnumerable<T> EmptyIfNull<T>(IEnumerable<T>? source) =>
    source ?? Array.Empty<T>();

// 6. 字符串键使用 StringComparer 而非默认
var dict = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

// 7. 使用 Span 切片避免 LINQ 在性能敏感路径的开销
public static int SumRange(int[] data, int start, int length)
{
    var slice = data.AsSpan(start, length);
    int sum = 0;
    foreach (var n in slice) sum += n;
    return sum; // 比 data.Skip(start).Take(length).Sum() 快 10 倍以上
}

实践 3:实现自定义可枚举类型

using System;
using System.Collections;
using System.Collections.Generic;

/// <summary>
/// 自定义分页序列:支持延迟求值与多次遍历。
/// 实现 IEnumerable<T> 使其与 LINQ 兼容。
/// </summary>
public sealed class PagedSequence<T> : IEnumerable<IEnumerable<T>>
{
    private readonly IEnumerable<T> _source;
    private readonly int _pageSize;

    public PagedSequence(IEnumerable<T> source, int pageSize)
    {
        if (pageSize <= 0)
            throw new ArgumentOutOfRangeException(nameof(pageSize));
        _source = source;
        _pageSize = pageSize;
    }

    public IEnumerator<IEnumerable<T>> GetEnumerator()
    {
        var page = new List<T>(_pageSize);
        foreach (var item in _source)
        {
            page.Add(item);
            if (page.Count == _pageSize)
            {
                yield return page;
                page = new List<T>(_pageSize);
            }
        }
        if (page.Count > 0)
            yield return page;
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class PaginationDemo
{
    public static void Run()
    {
        var numbers = Enumerable.Range(1, 25);
        var paged = new PagedSequence<int>(numbers, 10);

        foreach (var page in paged)
        {
            Console.WriteLine($"页 ({page.Count()}): {string.Join(", ", page)}");
        }
    }
}

实践 4:领域特定的泛型仓储

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;

/// <summary>泛型仓储接口:定义标准 CRUD 与查询。</summary>
public interface IRepository<T, in TKey> where T : class
{
    Task<T?> GetByIdAsync(TKey id);
    Task<IReadOnlyList<T>> FindAsync(Expression<Func<T, bool>> predicate);
    Task<IReadOnlyList<T>> GetAllAsync();
    Task<T> AddAsync(T entity);
    Task UpdateAsync(T entity);
    Task DeleteAsync(TKey id);
    Task<int> CountAsync(Expression<Func<T, bool>>? predicate = null);
}

/// <summary>内存仓储:用于单元测试与原型开发。</summary>
public class InMemoryRepository<T, TKey> : IRepository<T, TKey>
    where T : class
    where TKey : notnull
{
    private readonly Dictionary<TKey, T> _store = new();

    private readonly Func<T, TKey> _keySelector;

    public InMemoryRepository(Func<T, TKey> keySelector) => _keySelector = keySelector;

    public Task<T?> GetByIdAsync(TKey id) =>
        Task.FromResult(_store.TryGetValue(id, out var t) ? t : null);

    public Task<IReadOnlyList<T>> FindAsync(Expression<Func<T, bool>> predicate)
    {
        var compiled = predicate.Compile();
        var results = _store.Values.Where(compiled).ToList();
        return Task.FromResult<IReadOnlyList<T>>(results);
    }

    public Task<IReadOnlyList<T>> GetAllAsync() =>
        Task.FromResult<IReadOnlyList<T>>(_store.Values.ToList());

    public Task<T> AddAsync(T entity)
    {
        var key = _keySelector(entity);
        _store[key] = entity;
        return Task.FromResult(entity);
    }

    public Task UpdateAsync(T entity)
    {
        _store[_keySelector(entity)] = entity;
        return Task.CompletedTask;
    }

    public Task DeleteAsync(TKey id)
    {
        _store.Remove(id);
        return Task.CompletedTask;
    }

    public Task<int> CountAsync(Expression<Func<T, bool>>? predicate = null)
    {
        var count = predicate is null
            ? _store.Count
            : _store.Values.Count(predicate.Compile());
        return Task.FromResult(count);
    }
}

实践 5:高并发场景的 LRU 缓存

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;

/// <summary>
/// 线程安全的 LRU 缓存:基于 Dictionary + LinkedList,读写均加锁。
/// 适用于读多写多的中小规模缓存(数千到数十万项)。
/// </summary>
public sealed class LruCache<TKey, TValue> where TKey : notnull
{
    private readonly int _capacity;
    private readonly Dictionary<TKey, LinkedListNode<CacheItem>> _dict;
    private readonly LinkedList<CacheItem> _list;
    private readonly object _lock = new();

    public LruCache(int capacity)
    {
        if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity));
        _capacity = capacity;
        _dict = new Dictionary<TKey, LinkedListNode<CacheItem>>(capacity);
        _list = new LinkedList<CacheItem>();
    }

    public bool TryGet(TKey key, out TValue? value)
    {
        lock (_lock)
        {
            if (_dict.TryGetValue(key, out var node))
            {
                // 移动到链表头部(最近使用)
                _list.Remove(node);
                _list.AddFirst(node);
                value = node.Value.Value;
                return true;
            }
        }
        value = default;
        return false;
    }

    public void Set(TKey key, TValue value)
    {
        lock (_lock)
        {
            if (_dict.TryGetValue(key, out var node))
            {
                node.Value.Value = value;
                _list.Remove(node);
                _list.AddFirst(node);
            }
            else
            {
                if (_dict.Count >= _capacity)
                {
                    // 淘汰最久未使用(链表尾部)
                    var last = _list.Last!;
                    _list.RemoveLast();
                    _dict.Remove(last.Key);
                }
                var item = new CacheItem(key, value);
                var newNode = _list.AddFirst(item);
                _dict[key] = newNode;
            }
        }
    }

    public int Count
    {
        get { lock (_lock) return _dict.Count; }
    }

    private sealed class CacheItem
    {
        public TKey Key { get; }
        public TValue Value { get; set; }
        public CacheItem(TKey key, TValue value) { Key = key; Value = value; }
    }
}

/// <summary>
/// 无锁版 LRU 缓存:基于 ConcurrentDictionary,读不加锁。
/// 适合读远多于写的场景。
/// </summary>
public sealed class ConcurrentLruCache<TKey, TValue> where TKey : notnull
{
    private readonly int _capacity;
    private readonly ConcurrentDictionary<TKey, long> _accessTimes;
    private readonly ConcurrentDictionary<TKey, TValue> _values;
    private long _accessCounter;

    public ConcurrentLruCache(int capacity)
    {
        _capacity = capacity;
        _accessTimes = new ConcurrentDictionary<TKey, long>();
        _values = new ConcurrentDictionary<TKey, TValue>();
    }

    public bool TryGet(TKey key, out TValue? value)
    {
        if (_values.TryGetValue(key, out var v))
        {
            _accessTimes[key] = Interlocked.Increment(ref _accessCounter);
            value = v;
            return true;
        }
        value = default;
        return false;
    }

    public void Set(TKey key, TValue value)
    {
        _values[key] = value;
        _accessTimes[key] = Interlocked.Increment(ref _accessCounter);

        // 后台清理:低频触发,避免每次写入都扫描
        if (_values.Count > _capacity * 2)
        {
            var toRemove = _accessTimes
                .OrderBy(kv => kv.Value)
                .Take(_values.Count - _capacity)
                .Select(kv => kv.Key)
                .ToList();
            foreach (var k in toRemove)
            {
                _values.TryRemove(k, out _);
                _accessTimes.TryRemove(k, out _);
            }
        }
    }
}

案例研究

案例 1:日志分析系统的集合选型

场景:处理每日 10 亿条日志记录,需支持按时间范围查询、按关键词过滤、按用户聚合。

初始实现(性能瓶颈):

// 反模式:所有日志加载到 List<LogEntry>,每查询全扫描
public class LogStore
{
    private readonly List<LogEntry> _logs = new();

    public void Add(LogEntry log) => _logs.Add(log);

    public List<LogEntry> Query(DateTime start, DateTime end, string? keyword)
    {
        return _logs
            .Where(l => l.Timestamp >= start && l.Timestamp <= end)
            .Where(l => keyword is null || l.Message.Contains(keyword))
            .ToList(); // 10 亿条全扫描,单次查询分钟级
    }
}

优化版本(分层索引):

public class OptimizedLogStore
{
    // 时间索引:按小时分桶
    private readonly Dictionary<DateTime, List<LogEntry>> _byHour = new();

    // 用户索引:倒排
    private readonly Dictionary<string, HashSet<int>> _byUser = new();

    // 关键词倒排(分词后)
    private readonly Dictionary<string, HashSet<int>> _byKeyword = new();

    // 全量存储(按 ID)
    private readonly Dictionary<int, LogEntry> _byId = new();
    private int _nextId;

    public void Add(LogEntry log)
    {
        int id = _nextId++;
        _byId[id] = log;

        var hour = log.Timestamp.Date.AddHours(log.Timestamp.Hour);
        if (!_byHour.TryGetValue(hour, out var bucket))
        {
            bucket = new List<LogEntry>();
            _byHour[hour] = bucket;
        }
        bucket.Add(log);

        _byUser.GetOrAdd(log.UserId, _ => new HashSet<int>()).Add(id);

        foreach (var word in Tokenize(log.Message))
            _byKeyword.GetOrAdd(word, _ => new HashSet<int>()).Add(id);
    }

    public List<LogEntry> Query(DateTime start, DateTime end, string? keyword)
    {
        IEnumerable<int> candidates = null;

        // 时间过滤:直接定位小时桶
        var hours = EnumerateHours(start, end);
        candidates = hours.SelectMany(h =>
            _byHour.TryGetValue(h, out var b) ? b.Select(l => _nextId - b.Count + b.IndexOf(l)) : Enumerable.Empty<int>());

        // 关键词过滤:交集
        if (keyword is not null)
        {
            var words = Tokenize(keyword);
            foreach (var word in words)
            {
                if (_byKeyword.TryGetValue(word, out var ids))
                    candidates = candidates is null
                        ? ids.AsEnumerable()
                        : candidates.Intersect(ids);
                else
                    return new List<LogEntry>();
            }
        }

        return candidates
            .Where(id => _byId.TryGetValue(id, out var l) && l.Timestamp >= start && l.Timestamp <= end)
            .Select(id => _byId[id])
            .ToList();
    }

    private static List<string> Tokenize(string text) =>
        text.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList();

    private static IEnumerable<DateTime> EnumerateHours(DateTime start, DateTime end)
    {
        for (var h = start.Date.AddHours(start.Hour); h <= end; h = h.AddHours(1))
            yield return h;
    }
}

public record LogEntry(DateTime Timestamp, string UserId, string Message);

优化效果:

  • 时间范围查询从 O(n)O(n) 降至 O(k)O(k)(kk 为范围内日志数)
  • 关键词查询从 O(n)O(n) 降至 O(m⋅log⁡n)O(m \cdot \log n)(mm 为匹配数)
  • 内存占用增加约 3 倍(索引开销),但查询性能提升 1000 倍

案例 2:配置中心的多级缓存

场景:微服务架构中,配置中心需要将配置分发到数千个服务实例,配置项约 10 万条,要求查询 < 1ms。

架构:

public class ConfigCenter
{
    // L1: 每实例内存缓存(FrozenDictionary,启动时构建)
    private FrozenDictionary<string, ConfigValue>? _l1Cache;

    // L2: 本地持久化缓存(如 Redis),热启动加载
    private readonly Dictionary<string, ConfigValue> _l2Cache = new();

    // L3: 远程配置中心
    private readonly IConfigService _remoteService;

    public ConfigCenter(IConfigService remoteService) => _remoteService = remoteService;

    /// <summary>启动时加载并构建 L1 缓存。</summary>
    public async Task InitializeAsync()
    {
        var allConfigs = await _remoteService.LoadAllAsync();
        _l1Cache = allConfigs.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);
    }

    /// <summary>查询:优先 L1,未命中走 L2,再未命中走 L3。</summary>
    public async Task<ConfigValue?> GetAsync(string key)
    {
        // L1: FrozenDictionary,纳秒级
        if (_l1Cache?.TryGetValue(key, out var v) == true)
            return v;

        // L2: Dictionary,纳秒级
        if (_l2Cache.TryGetValue(key, out v))
            return v;

        // L3: 远程调用,毫秒级
        v = await _remoteService.GetAsync(key);
        if (v is not null)
        {
            _l2Cache[key] = v;
            // L1 不可变,需重建(异步触发)
            _ = RebuildL1Async();
        }
        return v;
    }

    /// <summary>定期重建 L1 缓存(后台任务)。</summary>
    private async Task RebuildL1Async()
    {
        var all = await _remoteService.LoadAllAsync();
        Interlocked.Exchange(ref _l1Cache, all.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase));
    }
}

public record ConfigValue(string Key, string Value, DateTime UpdatedAt);
public interface IConfigService
{
    Task<ConfigValue?> GetAsync(string key);
    Task<IReadOnlyDictionary<string, ConfigValue>> LoadAllAsync();
}

性能数据:

  • L1 命中:< 100ns(FrozenDictionary 高度优化)
  • L2 命中:< 200ns(Dictionary 查找)
  • L3 命中:~ 5ms(网络往返)
  • L1 命中率 > 99.9%,平均查询时间 < 200ns

案例 3:实时流处理的窗口聚合

场景:实时统计每分钟、每 5 分钟、每小时的活跃用户数、订单总额等指标。

实现:

using System.Collections.Concurrent;

public class SlidingWindowAggregator
{
    // 滑动窗口:键为窗口起始时间,值为聚合状态
    private readonly ConcurrentDictionary<long, WindowState> _windows = new();
    private readonly TimeSpan _windowSize;
    private readonly TimeSpan _retention;

    public SlidingWindowAggregator(TimeSpan windowSize, TimeSpan retention)
    {
        _windowSize = windowSize;
        _retention = retention;
    }

    public void Record(long timestamp, string userId, decimal amount)
    {
        long windowStart = AlignToWindow(timestamp, _windowSize.Ticks);
        var state = _windows.GetOrAdd(windowStart, _ => new WindowState());

        lock (state)
        {
            state.Users.Add(userId);
            state.TotalAmount += amount;
            state.Count++;
        }

        // 定期清理过期窗口
        CleanupOldWindows(timestamp);
    }

    public WindowStats? GetWindow(long timestamp)
    {
        long windowStart = AlignToWindow(timestamp, _windowSize.Ticks);
        if (_windows.TryGetValue(windowStart, out var state))
        {
            lock (state)
            {
                return new WindowStats(
                    state.Users.Count,
                    state.TotalAmount,
                    state.Count);
            }
        }
        return null;
    }

    private void CleanupOldWindows(long now)
    {
        var cutoff = now - _retention.Ticks;
        foreach (var kv in _windows)
        {
            if (kv.Key < cutoff)
                _windows.TryRemove(kv.Key, out _);
        }
    }

    private static long AlignToWindow(long timestamp, long windowTicks) =>
        timestamp / windowTicks * windowTicks;

    private sealed class WindowState
    {
        public HashSet<string> Users { get; } = new();
        public decimal TotalAmount { get; set; }
        public int Count { get; set; }
    }
}

public sealed record WindowStats(int ActiveUsers, decimal TotalAmount, int OrderCount);

关键设计:

  • ConcurrentDictionary 支持多生产者并发写入
  • 窗口内部加锁保证 HashSet 与计数器原子更新
  • 定期清理过期窗口避免内存膨胀
  • 桶对齐时间戳(除法取整)确保相同窗口事件进入同一桶

扩展:若需多级窗口(1min、5min、1hour),可使用多个聚合器实例,或采用层级降采样(downsampling)模式。

基础题

  1. 类型推断:给定方法签名 static T Find<T>(IEnumerable<T> source, Func<T, bool> predicate),分析调用 Find(new[] { "a", "bb", "ccc" }, s => s.Length > 1) 时编译器如何推断 T。

    • 参考答案要点:从 string[] 推断 T = string,Lambda 参数 s : string,返回 bool 匹配谓词签名。
  2. 集合选择:你需要实现一个频繁插入与删除(首尾均有)、偶尔按索引访问的场景。下列哪个集合最合适?

    • A) List<T> B) LinkedList<T> C) Dictionary<int, T> D) Queue<T>
    • 参考答案要点:B。LinkedList<T> 提供 O(1)O(1) 首尾增删,索引访问 O(n)O(n) 但偶发可接受。
  3. 协变逆变:判断下列哪些赋值合法并说明原因:

    • IEnumerable<object> = new List<string>()
    • IList<object> = new List<string>()
    • Action<object> = Action<string>(即 Action<in T>)
    • Func<string> = Func<object>(即 Func<out T>)
    • 参考答案要点:合法、非法(IList<T> 不变)、非法(逆变方向错误,应为 Action<string> = Action<object>)、合法(协变,Func<object> 可视为 Func<string>)。

进阶题

  1. 迭代器实现:编写一个泛型方法 Interleave<T>(IEnumerable<T> first, IEnumerable<T> second),交替返回两个序列的元素。当其中一个序列耗尽时,继续返回另一序列剩余元素。

    • 参考答案要点:使用 yield return,两个枚举器交替推进,处理任一先结束的情况。
  2. 性能优化:下列代码处理 100 万元素,运行耗时 800ms。分析瓶颈并优化至 50ms 以内。

    var dict = new Dictionary<string, int>();
    foreach (var s in largeList) // largeList 含 1M 字符串
        if (!dict.ContainsKey(s))
            dict[s] = 0;
        else
            dict[s]++;
    • 参考答案要点:ContainsKey + 索引器导致两次哈希查找,改为 CollectionsMarshal.GetValueRefOrAddDefault;预分配容量;考虑并行分批处理。
  3. 自定义集合:实现一个 BoundedQueue<T>,固定容量、线程安全、支持阻塞 Enqueue 与 Dequeue(满时阻塞入队、空时阻塞出队)。

    • 参考答案要点:使用 SemaphoreSlim 控制可用槽位,lock 保护内部 Queue<T>,WaitAsync 与 Release 配合实现阻塞语义。

挑战题

  1. 类型系统设计:设计一个支持”任意类型到任意类型”映射的 TypeMapper,要求:

    • 编译期类型安全(不能把 int 映射到 string 后取出 DateTime)
    • 运行时支持反射查询所有注册的映射
    • 提示:使用泛型接口 + 内部 Dictionary<Type, Dictionary<Type, object>> 存储委托
    • 参考答案要点:定义 IMapper<TFrom, TTo>,TypeMapper 内部用 Dictionary<(Type, Type), Delegate>,注册时 Register<TFrom, TTo>(Func<TFrom, TTo>),查询时 Map<TFrom, TTo>(TFrom),校验类型匹配。
  2. 持久化数据结构:实现一个简单的持久化向量(Persistent Vector),基于位分片树(bit-mapped trie),支持:

    • O(1)O(1) 随机访问
    • O(1)O(1) 末尾添加(结构共享)
    • 不可变更新(返回新版本)
    • 提示:参考 Clojure 的 PersistentVector,分支因子 32
    • 参考答案要点:32 叉树,深度 log⁡32n\log_{32} n,叶子节点存值,内部节点存子节点引用。更新时复制路径节点,未变子节点共享。
  3. 零分配迭代器:实现一个 RefEnumerable<T>,实现 IEnumerable<T> 但内部使用 Span<T> 与 ref 局部变量,避免装箱。讨论其与 Span<T>.GetEnumerator() 的差异。

    • 参考答案要点:Span<T> 的枚举器是 ref struct,无法作为 IEnumerable<T> 使用。可包装为 List<T>.Enumerator 风格,但需保证不跨 await。或使用 ReadOnlySpan<T> + 自定义结构枚举器,对外暴露 IEnumerable<T> 时会装箱。
  4. 并发哈希表设计:分析 ConcurrentDictionary 的分段锁设计,对比 Dictionary + 全局锁的性能。给定 8 核 CPU、1000 万次写入,预测两种方案的吞吐量比例。

    • 参考答案要点:ConcurrentDictionary 在 .NET 5+ 改为细粒度锁(每个桶独立锁或无锁读取 + CAS 写入),并发度随核数线性扩展。Dictionary + 锁在 8 核下吞吐量约为 ConcurrentDictionary 的 1/4 至 1/8(锁竞争导致串行化)。实测可设计 Benchmark 验证。

官方文档

经典教材

  • Jeffrey Richter. CLR via C# (4th Edition). Microsoft Press, 2012. Chapter 12: Generics, 289–323.
  • Jon Skeet. C# in Depth (4th Edition). Manning Publications, 2019. Chapter 3: Parameterized typing with generics, 58–92.
  • Andrew Troelsen, Phil Japikse. Pro C# 10 with .NET 6 (11th Edition). Apress, 2022. Chapter 10: Collections and Generics.
  • Joseph Albahari, Ben Albahari. C# 12 in a Nutshell. O’Reilly Media, 2024. Chapter 7: Collections, 281–340.
  • Mark Michaelis, Eric Lippert. Essential C# 12.0 (8th Edition). Addison-Wesley, 2024. Chapter 18: Generics, 645–690.

前沿论文与开源项目

相关模式与实践

实战项目

泛型类与方法

基本写法:泛型类定义 public class <类名><T> { ... }

// 定义类型参数化的泛型类
public class Repository<T>
{
    private readonly List<T> _items = [];
}

基本写法:泛型类带约束 public class <类名><T> where T : <约束> { ... }

// 定义带约束的泛型类
public class Repository<T> where T : class
{
    public T? FindById(int id) => _items.FirstOrDefault();
}

基本写法:泛型方法 public static <返回类型> <方法名><T>(<参数>)

// 方法定义类型参数
public static void Swap<T>(ref T a, ref T b)
{
    (a, b) = (b, a);
}

基本写法:泛型方法带约束 public static <返回类型> <方法名><T>(<参数>) where T : <约束>

// 带约束的泛型方法
public static T Max<T>(T a, T b) where T : IComparable<T>
{
    return a.CompareTo(b) >= 0 ? a : b;
}

基本写法:多类型参数泛型方法 public static <返回类型> <方法名><T1, T2>(<参数>)

// 多类型参数的泛型方法
public static TResult Convert<TInput, TResult>(
    TInput input, Func<TInput, TResult> converter)
{
    return converter(input);
}

泛型约束

基本写法:class 引用类型约束 where T : class

// 限制泛型参数为引用类型
public class Repository<T> where T : class { }

基本写法:struct 值类型约束 where T : struct

// 限制泛型参数为值类型
public class NumberProcessor<T> where T : struct { }

基本写法:new 无参构造约束 where T : new()

// 限制泛型参数有无参公共构造函数
public class Factory<T> where T : new()
{
    public T Create() => new T();
}

基本写法:基类约束 where T : <基类>

// 限制泛型参数继承自指定基类
public class AnimalProcessor<T> where T : Animal { }

基本写法:接口约束 where T : <接口>

// 限制泛型参数实现指定接口
public class Comparer<T> where T : IComparable<T> { }

基本写法:unmanaged 约束 where T : unmanaged

// 限制泛型参数为非托管类型
public class NativeBuffer<T> where T : unmanaged { }

单行写法:多约束 where <参数> : <约束1>, <约束2>

// 单行声明多个约束
public class Service<T> where T : class, ICloneable { }

换行写法:多类型参数多约束 where <参数1> : <约束> where <参数2> : <约束>

// 换行声明多类型参数的约束
public class Service<TInput, TOutput>
    where TInput : class, ICloneable
    where TOutput : new()
{
    public TOutput Process(TInput input) => new TOutput();
}

协变与逆变

基本写法:协变接口 interface <接口名><out T> { T <方法>(); }

// 协变接口允许子类型到父类型转换
public interface IProducer<out T>
{
    T Produce();
}

基本写法:协变接口转换 <父接口> <变量> = <实现类>;

// 协变允许 string producer 转换为 object producer
IProducer<object> producer = new StringProducer();

基本写法:IEnumerable 协变 IEnumerable<<父类型>> <变量> = <子类型集合>;

// 内置 IEnumerable<out T> 协变
IEnumerable<string> strings = new List<string> { "a", "b" };
IEnumerable<object> objects = strings;

基本写法:逆变接口 interface <接口名><in T> { void <方法>(T <参数>); }

// 逆变接口允许父类型到子类型转换
public interface IConsumer<in T>
{
    void Consume(T item);
}

基本写法:逆变接口转换 <子接口> <变量> = <实现类>;

// 逆变允许 object consumer 转换为 string consumer
IConsumer<string> consumer = new ObjectConsumer();

List<T>

基本写法:List 初始化 var <变量> = new List<<类型>> { <元素>, ... };

// 初始化列表
var list = new List<int> { 1, 2, 3, 4, 5 };

基本写法:List 添加元素 <列表>.Add(<元素>);

// 向列表末尾添加元素
list.Add(6);

基本写法:List 批量添加 <列表>.AddRange(<集合>);

// 批量添加元素到列表
list.AddRange([7, 8, 9]);

基本写法:List 指定位置插入 <列表>.Insert(<索引>, <元素>);

// 在指定索引位置插入元素
list.Insert(0, 0);

基本写法:List 索引访问 <类型> <变量> = <列表>[<索引>];

// 通过索引访问列表元素
int first = list[0];

基本写法:List 末尾访问 <类型> <变量> = <列表>[^1];

// 从末尾访问列表元素
int last = list[^1];

基本写法:List Contains 检查 bool <结果> = <列表>.Contains(<元素>);

// 检查列表是否包含指定元素
bool has = list.Contains(3);

基本写法:List IndexOf 定位 int <结果> = <列表>.IndexOf(<元素>);

// 获取元素首次出现的索引
int index = list.IndexOf(3);

基本写法:List Find 查找 <类型> <结果> = <列表>.Find(<谓词>);

// 查找第一个匹配条件的元素
int found = list.Find(x => x > 5);

基本写法:List FindAll 查找全部 List<<类型>> <结果> = <列表>.FindAll(<谓词>);

// 查找所有匹配条件的元素
List<int> allFound = list.FindAll(x => x > 3);

基本写法:List Remove 删除 <列表>.Remove(<元素>);

// 删除第一个匹配元素
list.Remove(3);

基本写法:List RemoveAt 索引删除 <列表>.RemoveAt(<索引>);

// 删除指定索引的元素
list.RemoveAt(0);

基本写法:List RemoveAll 条件删除 <列表>.RemoveAll(<谓词>);

// 删除所有匹配条件的元素
list.RemoveAll(x => x > 5);

基本写法:List 默认排序 <列表>.Sort();

// 使用默认比较器排序
list.Sort();

基本写法:List 自定义排序 <列表>.Sort(<比较器>);

// 使用自定义比较器降序排序
list.Sort((a, b) => b.CompareTo(a));

基本写法:List ForEach 遍历 <列表>.ForEach(<动作>);

// 对每个元素执行指定动作
list.ForEach(item => Console.WriteLine(item));

Dictionary<TKey, TValue>

基本写法:Dictionary 初始化 var <变量> = new Dictionary<<TKey>, <TValue>> { [<键>] = <值>, ... };

// 初始化字典
var dict = new Dictionary<string, int>
{
    ["apple"] = 5,
    ["banana"] = 3
};

基本写法:Dictionary Add 添加 <字典>.Add(<键>, <值>);

// 添加键值对(键不存在时)
dict.Add("grape", 4);

基本写法:Dictionary 索引赋值 <字典>[<键>] = <值>;

// 添加或更新键值对
dict["mango"] = 6;

基本写法:Dictionary TryAdd 尝试添加 bool <结果> = <字典>.TryAdd(<键>, <值>);

// 尝试添加键值对
dict.TryAdd("pear", 2);

基本写法:Dictionary 索引访问 <值类型> <变量> = <字典>[<键>];

// 通过键获取值(键不存在抛异常)
int count = dict["apple"];

基本写法:Dictionary TryGetValue 安全访问 bool <结果> = <字典>.TryGetValue(<键>, out <输出变量>);

// 安全获取值,返回是否成功
if (dict.TryGetValue("banana", out int value))
{
    Console.WriteLine($"banana: {value}");
}

基本写法:Dictionary 默认值访问 <值类型> <变量> = <字典>.GetValueOrDefault(<键>, <默认值>);

// 获取值或默认值
int safe = dict.GetValueOrDefault("kiwi", 0);

基本写法:Dictionary 遍历 foreach (var (<键>, <值>) in <字典>)

// 遍历字典的键值对
foreach (var (key, val) in dict)
{
    Console.WriteLine($"{key}: {val}");
}

基本写法:Dictionary ContainsKey 检查 bool <结果> = <字典>.ContainsKey(<键>);

// 检查字典是否包含指定键
bool hasKey = dict.ContainsKey("apple");

基本写法:Dictionary Remove 删除 <字典>.Remove(<键>);

// 删除指定键的键值对
dict.Remove("apple");

HashSet<T> 与其他集合

基本写法:HashSet 交集 <集合1>.IntersectWith(<集合2>);

// 计算两个集合的交集
var set1 = new HashSet<int> { 1, 2, 3, 4, 5 };
var set2 = new HashSet<int> { 4, 5, 6, 7, 8 };
set1.IntersectWith(set2);

基本写法:HashSet 并集 <集合1>.UnionWith(<集合2>);

// 计算两个集合的并集
set1.UnionWith(set2);

基本写法:HashSet 差集 <集合1>.ExceptWith(<集合2>);

// 计算两个集合的差集
set1.ExceptWith(set2);

基本写法:Queue 入队 <队列>.Enqueue(<元素>);

// 向队列末尾添加元素
var queue = new Queue<string>();
queue.Enqueue("第一个");

基本写法:Queue 出队 <类型> <变量> = <队列>.Dequeue();

// 从队列头部移除并返回元素
string dequeued = queue.Dequeue();

基本写法:Stack 压栈 <栈>.Push(<元素>);

// 向栈顶压入元素
var stack = new Stack<int>();
stack.Push(1);

基本写法:Stack 出栈 <类型> <变量> = <栈>.Pop();

// 从栈顶弹出元素
int popped = stack.Pop();

基本写法:PriorityQueue 入队 <队列>.Enqueue(<元素>, <优先级>);

// 按优先级入队
var pq = new PriorityQueue<string, int>();
pq.Enqueue("高优先级", 1);

基本写法:PriorityQueue 出队 <类型> <变量> = <队列>.Dequeue();

// 取出优先级最高的元素
string highest = pq.Dequeue();

基本写法:LinkedList 尾部添加 <链表>.AddLast(<元素>);

// 在链表末尾添加节点
var linked = new LinkedList<int>();
linked.AddLast(1);

基本写法:LinkedList 头部添加 <链表>.AddFirst(<元素>);

// 在链表头部添加节点
linked.AddFirst(0);

不可变集合

基本写法:ImmutableList 创建 var <变量> = ImmutableList.Create(<元素>, ...);

// 创建不可变列表
var list = ImmutableList.Create(1, 2, 3);

基本写法:ImmutableList 添加 var <新列表> = <列表>.Add(<元素>);

// 添加元素返回新列表,原列表不变
var newList = list.Add(4);

基本写法:Frozen 字典创建 var <变量> = <字典>.ToFrozenDictionary();

// 创建后不可修改,查询性能极快
var frozen = new Dictionary<string, int>
{
    ["a"] = 1, ["b"] = 2
}.ToFrozenDictionary();

LINQ to Objects

基本写法:Where 筛选 var <结果> = <集合>.Where(<谓词>);

// 筛选满足条件的元素
var expensive = products.Where(p => p.Price > 1000);

基本写法:Select 投影 var <结果> = <集合>.Select(<选择器>);

// 将元素投影为新形式
var names = products.Select(p => p.Name);

基本写法:Select 匿名类型投影 var <结果> = <集合>.Select(<选择器>);

// 投影为匿名类型
var projections = products.Select(p => new { p.Name, p.Price });

基本写法:OrderBy 排序 var <结果> = <集合>.OrderBy(<键选择器>);

// 按指定键升序排序
var sorted = products.OrderBy(p => p.Price);

基本写法:OrderByDescending 降序排序 var <结果> = <集合>.OrderByDescending(<键选择器>);

// 按指定键降序排序
var sortedDesc = products.OrderByDescending(p => p.Price);

基本写法:ThenBy 多级排序 var <结果> = <集合>.OrderBy(<键1>).ThenBy(<键2>);

// 多级排序
var multiSort = products.OrderBy(p => p.Category).ThenBy(p => p.Price);

基本写法:GroupBy 分组 var <结果> = <集合>.GroupBy(<键选择器>);

// 按指定键分组
var grouped = products.GroupBy(p => p.Category);

基本写法:Sum 求和 <类型> <结果> = <集合>.Sum(<选择器>);

// 计算指定属性的总和
decimal total = products.Sum(p => p.Price);

基本写法:Average 平均值 <类型> <结果> = <集合>.Average(<选择器>);

// 计算指定属性的平均值
decimal avg = products.Average(p => p.Price);

基本写法:Count 条件计数 int <结果> = <集合>.Count(<谓词>);

// 统计满足条件的元素数量
int count = products.Count(p => p.Price > 500);

基本写法:Skip Take 分页 var <结果> = <集合>.Skip(<数量>).Take(<数量>);

// 跳过指定数量后取指定数量
var page = products.OrderBy(p => p.Price).Skip(10).Take(5);

基本写法:Distinct 去重 var <结果> = <集合>.Select(<选择器>).Distinct();

// 对指定属性去重
var categories = products.Select(p => p.Category).Distinct();

基本写法:Intersect 交集 var <结果> = <集合1>.Intersect(<集合2>);

// 计算两个集合的交集
var intersect = allNames.Intersect(someNames);

基本写法:Union 并集 var <结果> = <集合1>.Union(<集合2>);

// 计算两个集合的并集
var union = allNames.Union(someNames);

基本写法:Except 差集 var <结果> = <集合1>.Except(<集合2>);

// 计算两个集合的差集
var except = allNames.Except(someNames);

单行写法:查询语法 from <变量> in <集合> where <条件> select <结果>

// 单行 SQL 风格查询表达式
var query = from p in products where p.Price > 500 select p;

换行写法:查询语法 from <变量> in <集合> where <条件> orderby <排序> select <结果>

// 换行 SQL 风格查询表达式
var query = from p in products
            where p.Price > 500
            orderby p.Price descending
            select new { p.Name, p.Price };

基本写法:Join 连接查询 from <变量1> in <集合1> join <变量2> in <集合2> on <键1> equals <键2> select <结果>

// 连接两个集合查询
var result = from p in products
             join o in orders on p.Name equals o.ProductName
             select new { p.Name, o.Quantity };

迭代器 (yield)

基本写法:yield return 惰性生成 yield return <值>;

// 惰性生成斐波那契数列
public static IEnumerable<int> Fibonacci(int count)
{
    int a = 0, b = 1;
    for (int i = 0; i < count; i++)
    {
        yield return a;
        (a, b) = (b, a + b);
    }
}

基本写法:yield break 提前退出 yield break;

// 遇到负数时停止生成
public static IEnumerable<int> GetPositive(int[] numbers)
{
    foreach (var n in numbers)
    {
        if (n < 0) yield break;
        if (n > 0) yield return n;
    }
}

基本写法:惰性读取文件 yield return <行>;

// 逐行惰性读取文件
public static IEnumerable<string> ReadLinesLazy(string path)
{
    using var reader = new StreamReader(path);
    while (reader.ReadLine() is string line)
    {
        yield return line;
    }
}

集合表达式 (C# 12)

基本写法:数组集合表达式 <类型>[] <变量> = [<元素>, ...];

// 使用集合表达式初始化数组
int[] array = [1, 2, 3];

基本写法:List 集合表达式 List<<类型>> <变量> = [<元素>, ...];

// 使用集合表达式初始化列表
List<string> list = ["a", "b", "c"];

基本写法:Span 集合表达式 Span<<类型>> <变量> = [<元素>, ...];

// 使用集合表达式初始化 Span
Span<int> span = [1, 2, 3];

基本写法:HashSet 集合表达式 HashSet<<类型>> <变量> = [<元素>, ...];

// 使用集合表达式初始化 HashSet
HashSet<string> set = ["x", "y", "z"];

基本写法:展开运算符合并 <类型>[] <变量> = [..<集合1>, ..<集合2>, <元素>];

// 使用展开运算符合并多个集合
int[] a = [1, 2, 3];
int[] b = [4, 5, 6];
int[] combined = [..a, ..b, 7, 8];