C# Language Énumérer les Enumerables


Exemple

L'interface IEnumerable <T> est l'interface de base pour tous les énumérateurs génériques et constitue une partie essentielle de la compréhension de LINQ. À la base, il représente la séquence.

Cette interface sous-jacente est héritée par toutes les collections génériques, telles que Collection <T> , Array , List <T> , Dictionary <TKey, TValue> Class et HashSet <T> .

En plus de représenter la séquence, toute classe qui hérite de IEnumerable <T> doit fournir un IEnumerator <T>. L'énumérateur expose l'itérateur à l'énumérable, et ces deux interfaces et idées interconnectées sont à l'origine du dicton "énumérer les énumérables".

"Enumerating the enumerable" est une phrase importante. L'énumérateur est simplement une structure pour savoir comment itérer, il ne contient aucun objet matérialisé. Par exemple, lors du tri, un énumérable peut contenir les critères du champ à trier, mais l'utilisation de .OrderBy() en lui-même renverra un IEnumerable <T> qui ne sait que trier. L'utilisation d'un appel qui matérialisera les objets, comme dans l'itération de l'ensemble, est appelée énumération (par exemple .ToList() ). Le processus d'énumération utilisera la définition énumérable de la manière de parcourir les séries et de renvoyer les objets pertinents (dans l'ordre, filtrés, projetés, etc.).

Ce n’est qu’une fois que l’énumérable a été énuméré que cela provoque la matérialisation des objets, c’est-à-dire lorsque des métriques telles que la complexité temporelle (durée liée à la taille des séries) et la complexité spatiale (quantité Être mesuré.

Créer votre propre classe qui hérite de IEnumerable <T> peut être un peu compliqué en fonction de la série sous-jacente qui doit être énumérée. En général, il est préférable d'utiliser l'une des collections génériques existantes. Cela dit, il est également possible d'hériter de l'interface IEnumerable <T> sans avoir un tableau défini comme structure sous-jacente.

Par exemple, utiliser la série Fibonacci comme séquence sous-jacente. Notez que l'appel à Where construit simplement un IEnumerable , et ce n'est qu'un appel à énumérer que énumérable est fait que l'une des valeurs est matérialisée.

void Main()
{
    Fibonacci Fibo = new Fibonacci();
    IEnumerable<long> quadrillionplus = Fibo.Where(i => i > 1000000000000);
    Console.WriteLine("Enumerable built");
    Console.WriteLine(quadrillionplus.Take(2).Sum());
    Console.WriteLine(quadrillionplus.Skip(2).First());

    IEnumerable<long> fibMod612 = Fibo.OrderBy(i => i % 612);
    Console.WriteLine("Enumerable built");
    Console.WriteLine(fibMod612.First());//smallest divisible by 612
}

public class Fibonacci : IEnumerable<long>
{
    private int max = 90;

    //Enumerator called typically from foreach
    public IEnumerator GetEnumerator() {
        long n0 = 1;
        long n1 = 1;
        Console.WriteLine("Enumerating the Enumerable");
        for(int i = 0; i < max; i++){
            yield return n0+n1;
            n1 += n0;
            n0 = n1-n0;
        }
    }
    
    //Enumerable called typically from linq
    IEnumerator<long> IEnumerable<long>.GetEnumerator() {
        long n0 = 1;
        long n1 = 1;
        Console.WriteLine("Enumerating the Enumerable");
        for(int i = 0; i < max; i++){
            yield return n0+n1;
            n1 += n0;
            n0 = n1-n0;
        }
    }
}

Sortie

Enumerable built
Enumerating the Enumerable
4052739537881
Enumerating the Enumerable
4052739537881
Enumerable built
Enumerating the Enumerable
14930352

La force du second ensemble (le fibMod612) est que même si nous avons appelé pour ordonner l’ensemble de nos nombres de Fibonacci, une seule valeur ayant été prise avec .First() la complexité du temps était O (n) comme une seule valeur. besoin d'être comparé lors de l'exécution de l'algorithme de commande. Ceci est dû au fait que notre enquêteur n'a demandé qu'une valeur, de sorte que l'intégralité de l'énumérateur n'a pas à être matérialisée. Si nous avions utilisé .Take(5) au lieu de .First() l'énumérateur aurait demandé 5 valeurs et au plus 5 valeurs devraient être matérialisées. Par rapport à la nécessité de commander un ensemble complet et de prendre ensuite les 5 premières valeurs, le principe d'économiser beaucoup de temps d'exécution et d'espace.