CodeGym /Cursos /C# SELF /Contrato de enumerabilidade:

Contrato de enumerabilidade: IEnumerable<T>

C# SELF
Nível 28 , Lição 1
Disponível

1. Introdução

Como você já deve ter percebido, várias coleções do C# têm características em comum. Por exemplo, quase todas elas podem ser percorridas usando o loop foreach:


var names = new List<string> { "Anya", "Boris", "Vika" };

// Atenção — esse código funciona com List, array e até com HashSet!
foreach (var name in names)
{
    Console.WriteLine(name);
}

Qual é a mágica? O segredo é que todas as coleções modernas implementam a interface IEnumerable<T> — um tipo de "contrato" que garante que a coleção sabe devolver os elementos um por um, em sequência.

Imagina uma empresa onde qualquer coleção de funcionários (seja um departamento, time de projeto, lista da festa da firma) segue uma regra: se o objeto implementa a interface "IEnumerable", então você sempre pode percorrer todos os funcionários numa ordem definida, não importa "como eles são guardados", o importante é que dá pra passar por todos.

2. Interface IEnumerable<T> — o que tem dentro?

Vamos ver como essa interface aparece no .NET:


public interface IEnumerable<out T>
{
    IEnumerator<T> GetEnumerator();
}

Ela tem apenas um métodoGetEnumerator, que devolve um objeto do tipo IEnumerator<T>. Esse objeto é o responsável pelo processo de "enumerar": ele sabe qual elemento está agora, como ir pro próximo, quando acabou tudo.

Resumindo: se uma classe (ou coleção) implementa IEnumerable<T>, então dá pra percorrer ela com o loop foreach, pegar os elementos um por um — e não importa como ela é feita por dentro: pode ser lista, hash table, até coisa salva no HD.

Mini-esquema


Coleção (tipo List<T>)
    |
    v
IEnumerable<T>
    |
    v
IEnumerator<T> (o "percorredor" real dos elementos)

3. Prática e universalidade do código

A maior vantagem desse contrato é a universalidade. Se uma função ou método recebe um IEnumerable<T> como parâmetro, isso significa que ele aceita qualquer tipo de coleção — de arrays e listas até hash sets, filas e até coleções customizadas do usuário!

Por exemplo, olha só uma função pra somar elementos:


// Dá pra somar qualquer conjunto de int que suporte IEnumerable<int>
int Sum(IEnumerable<int> numbers)
{
    int result = 0;
    foreach (var n in numbers)
        result += n;
    return result;
}

// Funciona com List<int>
var list = new List<int> { 1, 2, 3 };
Console.WriteLine(Sum(list));

// Funciona com array!
int[] array = { 4, 5, 6 };
Console.WriteLine(Sum(array));

// Funciona até com o resultado de um método que filtra elementos
Console.WriteLine(Sum(list.Where(x => x % 2 == 0))); // Usando LINQ

Exemplo da vida real: métodos universais

Imagina que você precisa criar uma utilidade pra achar a palavra mais longa em qualquer coleção de strings. Graças ao IEnumerable<string>, você pode fazer isso pra qualquer fonte — array, lista, resultado de métodos de filtro, etc.:


string FindLongest(IEnumerable<string> words)
{
    string longest = "";
    foreach (var word in words)
        if (word.Length > longest.Length)
            longest = word;
    return longest;
}

Dá pra usar com qualquer conjunto que encaixe.

4. Por que o loop foreach funciona com IEnumerable<T>?

Pergunta comum: por que o loop foreach "entende" qualquer coleção? Simples: o compilador do C# procura no classe o método GetEnumerator e espera receber um objeto com os métodos MoveNext() e a propriedade Current. Esse é o padrão da interface — IEnumerator<T>.

Por causa disso, você pode até criar sua própria classe que "entrega" elementos um por um (tipo um gerador de sequência de Fibonacci), e se ela implementar IEnumerable<int>, dá pra usar no foreach igualzinho a uma lista normal.

5. O que é Enumerator e como ele funciona?

Dentro de qualquer coleção que implementa IEnumerable<T>, tem um “percorredor” especial — o Enumerator (ou, no termo técnico: objeto que implementa a interface IEnumerator<T>). É ele que “puxa” os elementos da coleção um por um quando você faz o loop foreach.

Interface IEnumerator<T>

O que o Enumerator padrão sabe fazer:


public interface IEnumerator<T> : IDisposable
{
    T Current { get; }         // Elemento atual
    bool MoveNext();           // Vai pro próximo elemento
    void Reset();              // Volta pro começo (quase nunca usado)
}

Como funciona por dentro?

  • MoveNext() — move o "ponteiro" pro próximo elemento e retorna true se ainda tem elemento. Se acabou — retorna false.
  • Current — devolve o elemento atual (aquele que o enumerator está apontando agora).
  • Reset() — volta o Enumerator pro começo (mas quase nunca se usa).
  • Dispose() — libera recursos (importante pra coleções que mexem com arquivos ou rede).

Exemplo: como o foreach funciona "por baixo dos panos"

Quando você escreve:


var numbers = new List<int> { 1, 2, 3 };
foreach (var n in numbers)
    Console.WriteLine(n);

Na real, o compilador transforma isso mais ou menos assim:


var numbers = new List<int> { 1, 2, 3 };

// Pega o "percorredor"
var enumerator = numbers.GetEnumerator();
while (enumerator.MoveNext())
{
    var n = enumerator.Current;
    Console.WriteLine(n);
}
// O compilador chama Dispose() automaticamente no bloco using (se enumerator implementa IDisposable)

Importante: se a coleção mexe com recursos externos (tipo arquivos, banco de dados), o Enumerator pode liberar eles automaticamente quando termina o loop.

Esquema visual


Começo do loop -> GetEnumerator() -> Enumerator
         |
         v
  MoveNext() -> Current
         |
         v
  MoveNext() -> Current
         |
        ...
         |
         v
  MoveNext() == false -> fim do loop

6. IEnumerable e arrays, listas, conjuntos: quem é parente de quem

Vamos ver quais containers padrão do .NET implementam essa interface:

Tipo de coleção Implementa IEnumerable<T>? Dá pra usar o loop foreach?
Array (
int[]
)
List<T>
Dictionary<TKey, V>
✅ (por pares, chaves, valores)
HashSet<T>
Queue<T>
Stack<T>

Até string (string) implementa o IEnumerable normal, então dá pra percorrer cada caractere da string do mesmo jeito.

7. Implementando seu próprio Enumerable

Desafio legal: bora criar uma coleção mínima que guarda números pares de 0 até N e implementa IEnumerable<int>. Assim, dá pra percorrer ela no loop e usar com LINQ.


// Classe-coleção que pode ser "percorrida"
class EvenNumbers : IEnumerable<int>
{
    private int max;

    public EvenNumbers(int max)
    {
        this.max = max;
    }

    public IEnumerator<int> GetEnumerator()
    {
        for (int i = 0; i <= max; i += 2)
            yield return i; // Mágica especial pra implementar o enumerator
    }

    // Implementação explícita da interface IEnumerable não genérica pra compatibilidade
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

// Usando:
var evens = new EvenNumbers(10);
foreach(var e in evens)
    Console.Write($"{e} "); // 0 2 4 6 8 10

Detalhe chave: se sua classe implementa IEnumerable<T> — ela já fica compatível com quase tudo do .NET: LINQ, foreach, métodos que aceitam enumerable.

8. Erros comuns e pegadinhas

Às vezes, quem tá começando acha que IEnumerable<T> é uma coleção separada, que já tem elementos. Na real — é só uma "promessa" de que, se você começar a percorrer, ela vai "entregar" os elementos um por um.

Se você precisa de acesso aleatório por índice (myList[5]), usar métodos tipo Add ou Remove — a interface IEnumerable<T> não serve pra isso. Ela é só pra passar pelos elementos em sequência!

Erro: tentar modificar a coleção enquanto percorre. Tipo:


foreach (var item in myList)
{
    if (item < 0)
        myList.Remove(item); // PERIGO! InvalidOperationException
}

Melhor criar uma lista separada pra remover depois, ou usar métodos que criam uma nova coleção:


// Jeito seguro — cria uma nova coleção manualmente
var newList = new List<int>();
foreach (var item in myList)
{
    if (item >= 0)
        newList.Add(item);
}
myList = newList;

// Ou remove pelos índices de trás pra frente
for (int i = myList.Count - 1; i >= 0; i--)
{
    if (myList[i] < 0)
        myList.RemoveAt(i);
}
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION