Поделиться через


Коллекции

Среда выполнения .NET предоставляет множество типов коллекций, которые хранят группы связанных объектов и управляют ими. Некоторые типы коллекций, такие как System.ArraySystem.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> не.

Помимо этих характеристик среда выполнения предоставляет специализированные коллекции, которые препятствуют добавлению или удалению элементов или изменению элементов коллекции. Другие специализированные коллекции обеспечивают безопасность параллельного доступа в многопоточных приложениях.

Все типы коллекций можно найти в справочнике по API .NET. Дополнительные сведения см. в разделе "Часто используемые типы коллекций" и выбор класса коллекции.

Примечание.

В примерах этой статьи может потребоваться добавить директивы using для System.Collections.Generic пространств имен и System.Linq пространств имен.

Массивы представлены System.Array и поддерживают синтаксис на языке C#. Этот синтаксис предоставляет более краткие объявления для переменных массива.

System.Span<T>ref struct— это тип, предоставляющий моментальный снимок по последовательности элементов без копирования этих элементов. Компилятор применяет правила безопасности, чтобы убедиться, что Span доступ к ней невозможно получить после того, как последовательность, на которую она ссылается, больше не находится в область. Он используется во многих API .NET для повышения производительности. Memory<T> обеспечивает аналогичное поведение, если не удается использовать ref struct тип.

Начиная с C# 12, все типы коллекций можно инициализировать с помощью выражения Collection.

Индексируемые коллекции

Индексируемая коллекция — это коллекция , в которой можно получить доступ к каждому элементу с помощью его индекса. Его индекс — это количество элементов перед ним в последовательности. Таким образом, ссылка на элемент по индексу 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

В следующем примере элементы из списка удаляются по индексу. Вместо инструкции foreach используется for оператор, который выполняет итерацию в порядке убывания. Метод 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> можно также определить собственный класс. В приведенном ниже примере класс Galaxy, который используется объектом List<T>, определен в коде.

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 и свойство Item[]Dictionary для быстрого поиска элемента по ключу. Свойство Item позволяет получить доступ к элементу в коллекции elements с помощью кода elements[symbol] в C#.

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

В следующем примере метод используется TryGetValue для быстрого поиска элемента по ключу.

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

Итераторы

Итератор используется для выполнения настраиваемого перебора коллекции. Итератор может быть методом или методом доступа get. Итератор использует оператор yield return для возврата всех элементов коллекции по одному за раз.

Итератор вызывается с помощью оператора foreach. Каждая итерация цикла foreach вызывает итератор. При достижении оператора yield return в итераторе возвращается выражение, и текущее расположение в коде сохраняется. При следующем вызове итератора выполнение возобновляется с этого места.

Дополнительные сведения см. в разделе Итераторы (C#).

В приведенном ниже примере используется метод-итератор. Метод итератора содержит yield return инструкцию, которая находится внутри for цикла. В методе 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 обеспечивают возможности фильтрации, упорядочения и группировки. Дополнительные сведения см. в разделе Приступая к работе с LINQ в C#.

В приведенном ниже примере выполняется запрос LINQ применительно к универсальной коллекции List. Запрос 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}}
    };