Kolekcje

Środowisko uruchomieniowe platformy .NET udostępnia wiele typów kolekcji, które przechowują grupy powiązanych obiektów i zarządzają nimi. Niektóre typy kolekcji, takie jak System.Array, System.Span<T>i System.Memory<T> są rozpoznawane w języku C#. Ponadto interfejsy takie jak System.Collections.Generic.IEnumerable<T> są rozpoznawane w języku do wyliczania elementów kolekcji.

Kolekcje zapewniają elastyczny sposób pracy z grupami obiektów. Możesz sklasyfikować różne kolekcje według następujących cech:

  • Dostęp do elementów: każda kolekcja może być wyliczana w celu uzyskania dostępu do każdego elementu w kolejności. Niektóre kolekcje uzyskują dostęp do elementów według indeksu, pozycji elementu w uporządkowanej kolekcji. Najczęstszym przykładem jest System.Collections.Generic.List<T>. Inne kolekcje uzyskują dostęp do elementów według klucza, gdzie wartość jest skojarzona z pojedynczym kluczem. Najczęstszym przykładem jest System.Collections.Generic.Dictionary<TKey,TValue>. Wybierasz między tymi typami kolekcji na podstawie sposobu uzyskiwania dostępu do elementów przez aplikację.
  • Profil wydajności: każda kolekcja ma różne profile wydajności dla akcji, takich jak dodawanie elementu, znajdowanie elementu lub usuwanie elementu. Możesz wybrać typ kolekcji na podstawie operacji używanych najczęściej w aplikacji.
  • Dynamiczne zwiększanie i zmniejszanie: większość kolekcji obsługujących dynamiczne dodawanie lub usuwanie elementów. W szczególności , ArraySystem.Span<T>i System.Memory<T> nie.

Oprócz tych cech środowisko uruchomieniowe udostępnia wyspecjalizowane kolekcje, które uniemożliwiają dodawanie lub usuwanie elementów lub modyfikowanie elementów kolekcji. Inne wyspecjalizowane kolekcje zapewniają bezpieczeństwo współbieżnego dostępu w aplikacjach wielowątowych.

Wszystkie typy kolekcji można znaleźć w dokumentacji interfejsu API platformy .NET. Aby uzyskać więcej informacji, zobacz Często używane typy kolekcji i Wybieranie klasy kolekcji.

Uwaga

W przypadku przykładów w tym artykule może być konieczne dodanie dyrektyw using dla System.Collections.Generic przestrzeni nazw i System.Linq .

Tablice są reprezentowane przez System.Array i mają obsługę składni w języku C#. Ta składnia zapewnia bardziej zwięzłe deklaracje dla zmiennych tablicowych.

System.Span<T>ref struct to typ, który udostępnia migawkę sekwencji elementów bez kopiowania tych elementów. Kompilator wymusza reguły bezpieczeństwa, aby upewnić się, że Span nie można uzyskać dostępu do sekwencji, do których się odwołuje, nie jest już w zakresie. Jest on używany w wielu interfejsach API platformy .NET w celu zwiększenia wydajności. Memory<T> zapewnia podobne zachowanie, gdy nie można użyć ref struct typu.

Począwszy od języka C# 12, wszystkie typy kolekcji można zainicjować przy użyciu wyrażenia Kolekcja.

Kolekcje z możliwością indeksowania

Kolekcja z możliwością indeksowania to kolekcja , w której można uzyskać dostęp do każdego elementu przy użyciu jego indeksu. Jego indeks jest liczbą elementów przed nią w sekwencji. W związku z tym odwołanie do elementu według indeksu 0 jest pierwszym elementem, indeks 1 jest drugim i tak dalej. W tych przykładach użyto List<T> klasy . Jest to najbardziej typowa kolekcja z możliwością indeksowania.

Poniższy przykład tworzy i inicjuje listę ciągów, usuwa element i dodaje element na końcu listy. Po każdej modyfikacji iteruje ciągi przy użyciu instrukcji foreach lub for pętli:

// 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

Poniższy przykład usuwa elementy z listy według indeksu. foreach Zamiast instrukcji używa for instrukcji, która iteruje w kolejności malejącej. Metoda RemoveAt powoduje, że elementy po usunięciu elementu mają niższą wartość indeksu.

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

Dla typu elementów w obiekcie List<T>można również zdefiniować własną klasę. W poniższym przykładzie Galaxy klasa używana przez List<T> klasę jest zdefiniowana w kodzie.

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

Kolekcje par klucz/wartość

W tych przykładach użyto Dictionary<TKey,TValue> klasy . Jest to najbardziej typowa kolekcja słowników. Kolekcja słowników umożliwia dostęp do elementów w kolekcji przy użyciu klucza każdego elementu. Każdy dodatek do słownika składa się z wartości i skojarzonego z nim klucza.

Poniższy przykład tworzy Dictionary kolekcję i iteruje w słowniku przy użyciu foreach instrukcji .

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

W poniższym przykładzie użyto ContainsKey metody i Item[] właściwości Dictionary , aby szybko znaleźć element według klucza. Właściwość Item umożliwia dostęp do elementu w elements kolekcji przy użyciu elementu elements[symbol] w języku C#.

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

W poniższym przykładzie użyto metody , TryGetValue aby szybko znaleźć element według klucza.

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

Iteratory

Iterator służy do wykonywania iteracji niestandardowej w kolekcji. Iterator może być metodą lub akcesorem get . Iterator używa instrukcji zwrotu wydajności, aby zwrócić każdy element kolekcji pojedynczo.

Iterator jest wywoływany przy użyciu instrukcji foreach . Każda iteracja foreach pętli wywołuje iterator. yield return Po osiągnięciu instrukcji w iteratorze zwracane jest wyrażenie, a bieżąca lokalizacja w kodzie jest zachowywana. Wykonanie jest uruchamiane ponownie z tej lokalizacji przy następnym wywołaniu iteratora.

Aby uzyskać więcej informacji, zobacz Iteratory (C#).

W poniższym przykładzie użyto metody iteratora. Metoda iteratora zawiera instrukcję yield returnfor , która znajduje się wewnątrz pętli. W metodzie ListEvenNumbers każda iteracja foreach treści instrukcji tworzy wywołanie metody iteratora, która przechodzi do następnej yield return instrukcji.

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 i kolekcje

Zapytanie zintegrowane z językiem (LINQ) może służyć do uzyskiwania dostępu do kolekcji. Zapytania LINQ zapewniają możliwości filtrowania, porządkowania i grupowania. Aby uzyskać więcej informacji, zobacz Wprowadzenie do LINQ w języku C#.

Poniższy przykład uruchamia zapytanie LINQ względem ogólnego List. Zapytanie LINQ zwraca inną kolekcję zawierającą wyniki.

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