集合

集合

用于存储和管理相关对象的组。一些集合类型(例如 System.Array、System.Span 和 System.Memory)可使用 C# 语言识别。类似 System.Collections.Generic.IEnumerable 的接口可使用枚举集合元素的语言来识别。

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

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

除了这些特征之外,运行时还提供专用集合,这些集合可阻止添加或移除元素,或修改集合的元素。 其他专用集合为多线程应用中的并发访问提供安全性。

常用的集合类型:

集合类型表示收集数据的不同方式,例如哈希表、队列、堆栈、包、字典和列表。
所有集合都直接或间接基于 ICollection 或 ICollection 接口。 IList 和 IDictionary 及其泛型对应项均派生自这两个接口。

在基于 IList 或直接基于 ICollection 的集合中,每个元素都只包含一个值。 这些类型包括:

  • Array
  • ArrayList
  • List
  • Queue
  • ConcurrentQueue
  • Stack
  • ConcurrentStack
  • LinkedList

在基于 IDictionary 接口的集合中,每个元素都只包含一个键和一个值。 这些类型包括:

  • Hashtable
  • SortedList
  • SortedList<TKey,TValue>
  • Dictionary<TKey,TValue>
  • ConcurrentDictionary<TKey,TValue>

KeyedCollection<TKey,TItem> 类是抽象类,该类唯一的,因为它是值中嵌键的值的列表。因此,它的行为类似列表和字典。

可索引集合

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

以下示例会从一个泛型列表中按索引移除元素。 它使用以降序进行循环访问的 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.
numbers.ForEach(
    number => Console.Write(number + " "));
// Output: 0 2 4 6 8

键/值对集合

使用 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}}
    };

迭代器

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

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

下面的示例使用迭代器方法。 迭代器方法具有位于 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 查询提供筛选、排序和分组功能。

以下示例运行一个对泛型 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}}
    };

集合之间的异同之处

  1. 集合在存储、排序和比较元素以及执行搜索的方式方面有所不同。
  2. 所有集合中使用的索引都从零开始,Array 除外,它允许不从零开始的数组。
  3. SortedList 类和 SortedList<TKey,TValue> 泛型类提供 Hashtable 类和 Dictionary<TKey,TValue> 泛型类的已排序版本。
  4. 可以通过键或元素的索引访问 SortedList 或 KeyedCollection<TKey,TItem> 的元素。 只能通过元素的键访问 Hashtable 或 Dictionary<TKey,TValue> 的元素。
    以下是一个Array的不从0索引开始的集合
// 创建一个不从0开始的数组
Array myArray = Array.CreateInstance(typeof(int), [3], [1]);

选择合适的集合

请务必仔细选择需要的集合类。使用错误的类型可能会限制集合的使用。
注意:请避免使用 System.Collections 命名空间中的类型。 推荐使用泛型版本和并发版本的集合,因为它们的类型安全性很高,并且还经过了其他改进。

选择合适的集合可以根据以下方面考虑:

  • 是否需要顺序列表(其中通常在检索元素值后就将该元素丢弃)?。
  • 是否需要按索引访问每个元素?
  • 是否每个元素都包含一个值、一个键和一个值的组合或一个键和多个值的组合?
  • 是否需要以与输入方式不同的方式对元素进行排序?
  • 是否需要快速搜索和信息检索?
  • 是否需要只接受字符串的集合?
posted @ 2024-10-18 11:26  请明月  阅读(13)  评论(0编辑  收藏  举报