.NET 运行时提供了许多集合类型,用于存储和管理相关对象的组。 一些集合类型(例如 System.Array System.Span<T> System.Memory<T> )可使用 C# 语言识别。 此外,类似 System.Collections.Generic.IEnumerable<T> 的接口可使用枚举集合元素的语言来识别。

集合提供灵活的方式来使用对象组。 可按以下特征对不同的集合进行分类:

  • 元素访问 :可以枚举每个集合以按顺序访问每个元素。 某些集合可通过 索引 (元素在有序集合中的位置)访问元素。 最常见的示例是 System.Collections.Generic.List<T> 。 其他集合可按 访问元素,其中 与单个 相关联。 最常见的示例是 System.Collections.Generic.Dictionary<TKey,TValue> 。 可根据应用访问元素的方式在这些集合类型之间进行选择。
  • 性能配置文件 :每个集合都有不同的性能配置文件,可用于添加元素、查找元素或移除元素等操作。 可以根据应用中最常用的操作选取集合类型。
  • 动态增长和收缩 :大多数集合支持动态添加或移除元素。 需要注意的是, Array System.Span<T> System.Memory<T> 不支持。
  • 除了这些特征之外,运行时还提供专用集合,这些集合可阻止添加或移除元素,或修改集合的元素。 其他专用集合为多线程应用中的并发访问提供安全性。

    可以在 .NET API 参考 中找到所有集合类型。 有关其他信息,请参阅 常用集合类型 选择集合类

    对于本文中的示例,可能需要为 System.Collections.Generic System.Linq 命名空间添加 using directives

    数组 System.Array 表示,并受 C# 语言语法支持。 此语法为数组变量提供了更简洁的声明。

    System.Span<T> 属于 ref struct 类型,它可提供一系列元素的快照,而无需复制这些元素。 编译器强制实施安全规则,以确保在它引用的序列不再存在于作用域内之后无法访问 Span 。 它用于许多 .NET API 以提高性能。 Memory<T> 可在无法使用 ref struct 类型时提供类似的行为。

    从 C# 12 开始,可以使用 集合表达式 初始化所有集合类型。

    可索引集合

    可索引集合 是一个可以使用其索引访问每个元素的集合。 其 索引 是序列中在它之前的元素数。 因此,按索引 0 引用的元素是第一个元素,索引 1 则是第二个元素,依此而行。 这些示例使用 List<T> 类。 它是最常见的可索引集合。

    以下示例会创建和初始化字符串列表、移除元素并将元素添加到列表末尾。 每次修改后,它会使用 foreach 语句或 for 循环来循环访问字符串:

    // Create a list of strings by using a
    // collection initializer.
    List<string> salmons = ["chinook", "coho", "pink", "sockeye"];
    // Iterate through the list.
    foreach (var salmon in salmons)
        Console.Write(salmon + " ");
    // Output: chinook coho pink sockeye
    // Remove an element from the list by specifying
    // the object.
    salmons.Remove("coho");
    // Iterate using the index:
    for (var index = 0; index < salmons.Count; index++)
        Console.Write(salmons[index] + " ");
    // Output: chinook pink sockeye
    // Add the removed element
    salmons.Add("coho");
    // Iterate through the list.
    foreach (var salmon in salmons)
        Console.Write(salmon + " ");
    // Output: chinook pink sockeye coho
    

    以下示例会从一个泛型列表中按索引移除元素。 它使用以降序进行循环访问的 for 语句,而不是 foreach 语句。 RemoveAt 方法将导致已移除元素后的元素索引值减小。

    List<int> numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
    // Remove odd numbers.
    for (var index = numbers.Count - 1; index >= 0; index--)
        if (numbers[index] % 2 == 1)
            // Remove the element by specifying
            // the zero-based index in the list.
            numbers.RemoveAt(index);
    // Iterate through the list.
    // A lambda expression is placed in the ForEach method
    // of the List(T) object.
    numbers.ForEach(
        number => Console.Write(number + " "));
    // Output: 0 2 4 6 8
    

    对于 List<T> 中的元素类型,还可以定义自己的类。 在下面的示例中,由 List<T> 使用的 Galaxy 类在代码中定义。

    private static void IterateThroughList()
        var theGalaxies = new List<Galaxy>
            new (){ Name="Tadpole", MegaLightYears=400},
            new (){ Name="Pinwheel", MegaLightYears=25},
            new (){ Name="Milky Way", MegaLightYears=0},
            new (){ Name="Andromeda", MegaLightYears=3}
        foreach (Galaxy theGalaxy in theGalaxies)
            Console.WriteLine(theGalaxy.Name + "  " + theGalaxy.MegaLightYears);
        // Output:
        //  Tadpole  400
        //  Pinwheel  25
        //  Milky Way  0
        //  Andromeda  3
    public class Galaxy
        public string Name { get; set; }
        public int MegaLightYears { get; set; }
    

    键/值对集合

    这些示例使用 Dictionary<TKey,TValue> 类。 这是最常见的字典集合。 使用字典集合,可通过使用每个元素的键访问集合中的元素。 每次对字典的添加都包含一个值和与其关联的键。

    以下示例创建 Dictionary 集合并通过使用 foreach 语句循环访问字典。

    private static void IterateThruDictionary()
        Dictionary<string, Element> elements = BuildDictionary();
        foreach (KeyValuePair<string, Element> kvp in elements)
            Element theElement = kvp.Value;
            Console.WriteLine("key: " + kvp.Key);
            Console.WriteLine("values: " + theElement.Symbol + " " +
                theElement.Name + " " + theElement.AtomicNumber);
    public class Element
        public required string Symbol { get; init; }
        public required string Name { get; init; }
        public required int AtomicNumber { get; init; }
    private static Dictionary<string, Element> BuildDictionary() =>
        new ()
            {"K",
                new (){ Symbol="K", Name="Potassium", AtomicNumber=19}},
            {"Ca",
                new (){ Symbol="Ca", Name="Calcium", AtomicNumber=20}},
            {"Sc",
                new (){ Symbol="Sc", Name="Scandium", AtomicNumber=21}},
            {"Ti",
                new (){ Symbol="Ti", Name="Titanium", AtomicNumber=22}}
    

    以下示例使用 ContainsKey 方法和 DictionaryItem[] 属性按键快速查找某个项。 使用 Item 属性可通过 C# 中的 elements[symbol] 来访问 elements 集合中的项。

    if (elements.ContainsKey(symbol) == false)
        Console.WriteLine(symbol + " not found");
        Element theElement = elements[symbol];
        Console.WriteLine("found: " + theElement.Name);
    

    与之相反,以下示例使用 TryGetValue 方法按键快速查找某个项。

    if (elements.TryGetValue(symbol, out Element? theElement) == false)
        Console.WriteLine(symbol + " not found");
        Console.WriteLine("found: " + theElement.Name);
    

    迭代器用于对集合执行自定义迭代。 迭代器可以是一种方法,或是一个 get 访问器。 迭代器使用 yield return 语句返回集合的每一个元素,每次返回一个元素。

    通过使用 foreach 语句调用迭代器。 foreach 循环的每次迭代都会调用迭代器。 迭代器中到达 yield return 语句时,会返回一个表达式,并保留当前在代码中的位置。 下次调用迭代器时,将从该位置重新开始执行。

    有关详细信息,请参阅迭代器 (C#)

    下面的示例使用迭代器方法。 迭代器方法具有位于 for 循环中的 yield return 语句。 在 ListEvenNumbers 方法中,foreach 语句体的每次迭代都会创建对迭代器方法的调用,并将继续到下一个 yield return 语句。

    private static void ListEvenNumbers()
        foreach (int number in EvenSequence(5, 18))
            Console.Write(number.ToString() + " ");
        Console.WriteLine();
        // Output: 6 8 10 12 14 16 18
    private static IEnumerable<int> EvenSequence(
        int firstNumber, int lastNumber)
        // Yield even numbers in the range.
        for (var number = firstNumber; number <= lastNumber; number++)
            if (number % 2 == 0)
                yield return number;
    

    LINQ 和集合

    可以使用语言集成查询 (LINQ) 来访问集合。 LINQ 查询提供筛选、排序和分组功能。 有关详细信息,请参阅 C# 中的 LINQ 入门

    以下示例运行一个对泛型 List 的 LINQ 查询。 LINQ 查询返回一个包含结果的不同集合。

    private static void ShowLINQ()
        List<Element> elements = BuildList();
        // LINQ Query.
        var subset = from theElement in elements
                     where theElement.AtomicNumber < 22
                     orderby theElement.Name
                     select theElement;
        foreach (Element theElement in subset)
            Console.WriteLine(theElement.Name + " " + theElement.AtomicNumber);
        // Output:
        //  Calcium 20
        //  Potassium 19
        //  Scandium 21
    private static List<Element> BuildList() => new()
            { new(){ Symbol="K", Name="Potassium", AtomicNumber=19}},
            { new(){ Symbol="Ca", Name="Calcium", AtomicNumber=20}},
            { new(){ Symbol="Sc", Name="Scandium", AtomicNumber=21}},
            { new(){ Symbol="Ti", Name="Titanium", AtomicNumber=22}}