web-dev-qa-db-fra.com

IEnumerable Where () et ToList () - Que font-ils vraiment?

Je me demandais ce que font exactement les méthodes Where() et ToList(). Plus précisément, je me demandais si la Where() créerait un nouvel objet en mémoire ou renverrait un nouvel objet.

Ok, en regardant le code suivant, disons que j'ai une classe de journal squelette.

public class Log()
{
    public string Log {get;set;}
    public string CreatedByUserId {get;set;}
    public string ModifiedUserId {get;set;}
}

Dans ma logique métier, disons que je ne veux que les journaux créés ou modifiés par un utilisateur donné. Cela va être accompli avec une méthode: FilterLogsAccordingToUserId().

public IEnumerable<Log> FilterLogsAccordingToUserId(IEnumerable<Log> logs, string userId)
{
    int user = int.Parse(userId);
    return logs.Where(x => x.CreatedByUserId.Equals(user) ||
                           x.ModifiedByUserId.Equals(user)).ToList();
}

Dans cette situation, Where() modifie-t-il le IEnumerable<Log> en supprimant tous les objets qui ne correspondent pas à la condition ou saisit-il tous les objets, transforme-t-il cet objet en liste et le renvoie-t-il ensuite?

Si c'est la deuxième possibilité, ai-je raison de me préoccuper des performances si une liste suffisamment longue de journaux est transmise à la fonction?

10
Alexander Matusiak

Prenons les deux méthodes séparément.

Celui-ci renverra un nouvel objet qui, une fois énuméré, filtrera l'objet de collection d'origine par le prédicat.

Cela ne changera en aucun cas la collection originale, mais elle y sera liée.

Il s’agit également d’une collection d’exécution différée, ce qui signifie que jusqu’à ce que vous l’énumériez et que chaque fois que vous l’énumériez, elle utilisera la collection originale et filtrera celle-ci.

Cela signifie que si vous modifiez la collection d'origine, le résultat filtré changera en conséquence.

Voici un programme simple LINQPad qui montre:

void Main()
{
    var original = new List<int>(new[] { 1, 2, 3, 4 });
    var filtered = original.Where(i => i > 2);
    original.Add(5);
    filtered.Dump();
    original.Add(6);
    filtered.Dump();
}

Sortie:

LINQPad output #1

Comme vous pouvez le constater, l'ajout d'éléments à la collection d'origine qui répondent aux conditions de filtrage de la deuxième collection fera également apparaître ces éléments dans la collection filtrée.

Lister

Cela créera un nouvel objet de liste, le remplira avec la collection et renverra cette collection.

C'est une méthode immédiate, ce qui signifie qu'une fois que vous avez cette liste, celle-ci est maintenant une liste complètement distincte de la collection d'origine.

Notez que les objets in de cette liste peuvent toujours être partagés avec la collection d'origine. La méthode ToList ne crée pas de nouvelles copies de toutes ces copies, mais la collection est une nouvelle.

Voici un programme simple LINQPad qui montre:

void Main()
{
    var original = new List<int>(new[] { 1, 2, 3, 4 });
    var filtered = original.Where(i => i > 2).ToList();
    original.Add(5);

    original.Dump();
    filtered.Dump();
}

Sortie:

LINQPad output #2

Ici, vous pouvez voir qu'une fois que nous avons créé cette liste, elle ne change pas si la collection d'origine change.

Vous pouvez penser à la méthode Where comme étant liée à la collection d'origine, alors que ToList renverra simplement une nouvelle liste avec les éléments et ne sera pas liée à la collection d'origine.

Maintenant, regardons votre dernière question. Devez-vous vous inquiéter de la performance? Eh bien, c’est un sujet assez vaste, mais oui, vous devriez vous inquiéter pour la performance, mais pas au point de le faire tout le temps.

Si vous attribuez une collection grande à un appel Where, toutes les fois que vous énumérez les résultats de l'appel Where, vous énumérerez la grande collection d'origine et vous la filtrerez. Si le filtre ne laisse passer que quelques-uns de ces éléments, il énumérera toujours la grande collection d'origine à chaque énumération.

D'autre part, faire une ToList sur quelque chose de grand créera également une longue liste.

Est-ce que ça va être un problème de performance?

Qui peut dire, mais pour tout ce qui concerne les performances, voici ma réponse numéro 1:

  1. Sachez d'abord que vous avez un problème
  2. Deuxièmement, mesurez votre code en utilisant l’outil approprié (mémoire, temps de calcul, etc.) pour comprendre le problème de performances est:
  3. Répare le
  4. Retournez au numéro 1

Trop souvent, vous verrez des programmeurs s'inquiéter d'un morceau de code, pensant que cela posera un problème de performances, mais que l'utilisateur lent qui regarde l'écran se demande quoi faire, ou que le temps de téléchargement des données, ou le temps nécessaire pour écrire les données sur le disque, ou pas.

D'abord, vous savez, ensuite vous corrigez.

Where() renvoie un nouveau IEnumerable. C'est une version filtrée (une projection) de la séquence d'origine, et l'original est laissé inchangé. ToList() renvoie une nouvelle liste à l'aide de la projection.

Il est également important de noter que l'appel de .Where() n'évalue pas la projection, cela est fait lorsque l'énumérable est énuméré. Par exemple, lorsqu'il est utilisé dans une boucle foreach ou, dans ce cas, lors de l'appel de ToList().

6
Ed S.

Where filtre un IEnumerable<T> pour ne conserver que les éléments satisfaisant un prédicat, en maintenant l'ordre. Ceci fait not force l'énumération de la source IEnumerable<T>, elle est donc de nature déclarative.

public static IEnumerable<TSource> Where<TSource>(
    this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    foreach (var item in source)
    {
        if (predicate(item))
        {
            yield return item;
        }
    }
}

ToList convertit un IEnumerable<T> en List<T>, en maintenant l'ordre. Cela force l'énumération de la source IEnumerable<T> entière. 

public static List<TSource> ToList<TSource>(IEnumerable<TSource> source)
{
    var list = new List<TSource>();
    foreach (var item in source)
    {
        list.Add(item);
    }
    return list;
}

Dans ce cas, est-ce que .WHERE modifie les journaux IEnumerable en supprimant tous les objets qui ne correspondent pas à la condition ou est-il en train de récupérer tous les objets des journaux, en transformant cet objet en liste, puis en renvoyant ce nouvel objet?

Votre requête de la forme logs.Where(...).ToList() transmettra en flux vos éléments de journal via la partie Where, puis placera uniquement ceux qui satisfont le prédicat dans le List<Log> final.

3
Timothy Shields

Where créera un itérateur qui énumérera votre collection et ne renverra que les éléments correspondant à votre prédicat. Le point clé ici est que cette itération ne sera pas exécutée tant que vous n’aurez pas réellement essayé d’y accéder (dans une boucle foreach, par exemple).

ToList va cependant copier les références à chaque élément de votre collection énumérable dans une nouvelle liste (vous ne copiez pas le reference pas l’objet lui-même). Si vous insérez une ToList à la fin d'une Where, vous obligerez alors la Where à parcourir la collection.

En bref, si vous utilisez Where, vous ne créerez aucun nouvel objet (autre que l'itérateur lui-même) et vous ne changerez rien dans votre collection d'origine. Si vous utilisez ToList, vous copiez les references sur les objets correspondant à votre clause Where dans une nouvelle List (la liste d'origine reste, bien entendu, inchangée - à moins que vous ne l'ayez réattribuée à la même variable).

Donc, si vous n'avez pas réellement besoin de créer une nouvelle liste, n'utilisez pas ToList. Si tout ce que vous avez à faire est de parcourir votre collection, ignorez simplement la partie ToList. Cependant, il y a un point subtil ici, si vous faites ceci:

var filtered = logs.Where(x => x.CreatedByUserId.Equals(user) ||
                       x.ModifiedByUserId.Equals(user));

puis changez votre collection, puis procédez comme suit:

foreach (var f in filtered) 
{
   //....
}

Vous parcourerez votre collection d'origine (logs) telle qu'elle est maintenant et non telle qu'elle était lorsque vous avez déclaré filtered

0
Matt Burland