Coleções

O tempo de execução do .NET fornece muitos tipos de coleção que armazenam e gerenciam grupos de objetos relacionados. Alguns dos tipos de coleção, como System.Array, System.Span<T> e System.Memory<T> são reconhecidos na linguagem C#. Além disso, interfaces como System.Collections.Generic.IEnumerable<T> são reconhecidas na linguagem para enumerar os elementos de uma coleção.

As coleções fornecem uma maneira flexível de trabalhar com grupos de objetos. Você pode classificar diferentes coleções por estas características:

  • Acesso ao elemento: cada coleção pode ser enumerada para acessar cada elemento em ordem. Algumas coleções acessam elementos por índice, a posição do elemento em uma coleção ordenada. O exemplo mais comum é System.Collections.Generic.List<T>. Outras coleções acessam elementos por chave, onde um valor está associado a uma única chave. O exemplo mais comum é System.Collections.Generic.Dictionary<TKey,TValue>. Você escolhe entre esses tipos de coleção com base em como seu aplicativo acessa elementos.
  • Perfil de desempenho: cada coleção tem perfis de desempenho diferentes para ações como adicionar um elemento, localizar um elemento ou remover um elemento. Você pode escolher um tipo de coleção com base nas operações mais usadas em seu aplicativo.
  • Aumentar e reduzir dinamicamente: a maioria das coleções oferece suporte à adição ou remoção de elementos dinamicamente. Notavelmente, Array, System.Span<T> e System.Memory<T> não.

Além dessas características, o tempo de execução fornece coleções especializadas que impedem a adição ou remoção de elementos ou a modificação dos elementos da coleção. Outras coleções especializadas oferecem segurança para acesso simultâneo em aplicativos multi-thread.

Você pode encontrar todos os tipos de coleção na referência da API do .NET. Para obter mais informações, consulte Tipos de coleção comumente usados e Selecionando uma classe de coleção.

Observação

Para obter os exemplos neste artigo, talvez seja necessário adicionar usando diretivas para os namespaces System.Collections.Generic e System.Linq.

Matrizes são representadas por System.Array e têm suporte a sintaxe na linguagem C#. Essa sintaxe fornece declarações mais concisas para variáveis de matriz.

System.Span<T> é um tipo ref struct que fornece um instantâneo sobre uma sequência de elementos sem copiar esses elementos. O compilador impõe regras de segurança para garantir que o Span não possa ser acessado depois que a sequência a que ele faz referência não estiver mais no escopo. Ele é usado em muitas APIs do .NET para melhorar o desempenho. O Memory<T> fornece comportamento semelhante quando você não pode usar um tipo de ref struct.

A partir do C# 12, todos os tipos de coleção podem ser inicializados usando uma expressão de Coleção.

Coleções indexáveis

Uma coleção indexável é aquela em que você pode acessar cada elemento usando seu índice. Seu índice é o número de elementos antes dele na sequência. Portanto, o elemento de referência por índice 0 é o primeiro elemento, índice 1 é o segundo, e assim por diante. Esses exemplos usam a classe List<T>. É a coleção indexável mais comum.

O exemplo a seguir cria e inicializa uma lista de cadeias de caracteres, remove um elemento e adiciona um elemento ao final da lista. Após cada modificação, ele itera pelas cadeias de caracteres usando uma instrução foreach ou um loop 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

O exemplo a seguir remove elementos de uma lista por índice. Em vez de uma instrução foreach, ela usa uma instrução for que itera em ordem decrescente. O método RemoveAt faz com que os elementos após um elemento removido tenham um valor de índice mais baixo.

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

Para o tipo dos elementos na List<T>, você também pode definir sua própria classe. No exemplo a seguir, a classe Galaxy que é usada pela List<T> é definida no código.

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

Coleções de pares chave/valor

Esses exemplos usam a classe Dictionary<TKey,TValue>. É a coleção de dicionários mais comum. Uma coleção de dicionário permite que você acesse elementos na coleção usando a chave de cada elemento. Cada adição ao dicionário consiste em um valor e a respectiva chave associada.

O exemplo a seguir cria uma coleção Dictionary e itera no dicionário usando uma instrução 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}}
    };

O exemplo a seguir usa o método ContainsKey e a propriedade Item[] de Dictionary para localizar rapidamente um item por chave. A propriedade Item permite que você acesse um item na coleção elements usando o elements[symbol] no C#.

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

Em vez disso, o exemplo a seguir usa o método TryGetValue para localizar rapidamente um item por chave.

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

Iterators

Um iterador é usado para realizar uma iteração personalizada em uma coleção. Um iterador pode ser um método ou um acessador get. Um iterador usa uma instrução yield return para retornar um elemento da coleção por vez.

Você chama um iterador usando uma instrução foreach. Cada iteração do loop foreach chama o iterador. Quando uma instrução yield return é alcançada no iterador, uma expressão é retornada e o local atual no código é retido. A execução será reiniciada desse local na próxima vez que o iterador for chamado.

Para obter mais informações, consulte Iteradores (C#).

O exemplo a seguir usa um método iterador. O método iterador tem uma instrução yield return que está dentro de um loop for. No método ListEvenNumbers, cada iteração do corpo da instrução foreach cria uma chamada ao método iterador, que avança para a próxima instrução 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 e coleções

A consulta integrada à linguagem (LINQ) pode ser usada para acessar coleções. As consultas LINQ fornecem recursos de filtragem, classificação e agrupamento. Para obter mais informações, confira Introdução à LINQ no C#.

O exemplo a seguir executa uma consulta LINQ em uma List genérica. A consulta LINQ retorna uma coleção diferente que contém os resultados.

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