390 votes

La manipulation de l'avertissement d'une possible plusieurs énumération de IEnumerable

Dans mon code dans le besoin d'utiliser un IEnumerable<> plusieurs fois ainsi obtenir la Resharper erreur de "multiples énumération de IEnumerable".

Exemple de code:

public List<object> Foo(IEnumerable<object> objects)
{
    if (objects == null || !objects.Any())
        throw new ArgumentException();

    var firstObject = objects.First();
    var list = DoSomeThing(firstObject);        
    var secondList = DoSomeThingElse(objects);
    list.AddRange(secondList);

    return list;
}
  • Je peux changer les objets paramètre List puis éviter de multiples énumération, mais alors je ne comprends pas la plus haute de l'objet que je peux gérer.
  • Une autre chose que je peux faire est de convertir l' IEnumerable de List dans le début de la méthode:

 public List<object> Foo(IEnumerable<object> objects)
 {
    var objectList = objects.ToList();
    // ...
 }

Mais c'est juste maladroit.

Que feriez-vous dans ce scénario?

504voto

Paul Stovell Points 14880

Le problème avec la prise IEnumerable comme un paramètre, c'est qu'il indique appelants "je tiens à énumérer ce". Il ne dit pas quel est le nombre de fois que vous le souhaitez à énumérer.

Je peux changer les objets paramètre à la Liste et puis éviter les multiples énumération, mais alors je ne comprends pas la plus haute de l'objet que je peux gérer.

L'objectif de la prise la plus haute de l'objet est noble, mais il laisse de la place pour un trop grand nombre d'hypothèses. Voulez-vous vraiment quelqu'un pour passer un LINQ to SQL de la requête à cette méthode, seulement pour vous les énumérer deux fois (potentiellement des résultats différents à chaque fois?)

La sémantique qui manque ici, c'est que l'appelant, qui n'a peut-être pas prendre le temps de lire les détails de la méthode, peuvent supposer que vous seulement itérer une fois, de sorte qu'ils vous passent un cher objet. Votre signature de la méthode n'indique pas de toute façon.

Par la modification de la signature de la méthode de IList/ICollection, vous aurez au moins le rendre plus clair pour l'appelant quelles sont vos attentes, et ils peuvent éviter des erreurs coûteuses.

Sinon, la plupart des développeurs à la recherche à la méthode pourrait supposer que vous ne itérer une fois. Si vous prenez un IEnumerable est si important, vous devriez envisager de le faire à l' .ToList() au début de la méthode.

C'est une honte .NET ne dispose pas d'une interface IEnumerable + Count + indexeur, sans Ajouter/Supprimer etc. les méthodes, ce qui est ce que je soupçonne, il permettrait de résoudre ce problème.

32voto

Marc Gravell Points 482669

Si vos données est toujours va être reproductible, peut-être ne vous inquiétez pas à ce sujet. Cependant, vous pouvez dérouler trop - ce qui est particulièrement utile si les données entrantes pourrait être de grande taille (par exemple, la lecture à partir du disque/réseau):

if(objects == null) throw new ArgumentException();
using(var iter = objects.GetEnumerator()) {
    if(!iter.MoveNext()) throw new ArgumentException();

    var firstObject = iter.Current;
    var list = DoSomeThing(firstObject);  

    while(iter.MoveNext()) {
        list.Add(DoSomeThingElse(iter.Current));
    }
    return list;
}

Remarque j'ai changé la sémantique de DoSomethingElse un peu, mais c'est surtout pour montrer déroulé d'utilisation. Vous pourriez re-wrap l'itérateur, par exemple. Vous pourriez faire un itérateur bloc de trop, ce qui pourrait être sympa; alors il n'y a pas d' list - et vous seriez yield return les articles que vous les obtenez, plutôt que de l'ajouter à une liste pour être retourné.

2voto

João Angelo Points 24422

Si le but est vraiment d'éviter que plusieurs énumérations que la réponse de Marc Gravel est le seul à lire, mais en conservant la même sémantique que vous pourriez simple de supprimer la redondant Any et First des appels et aller avec:

public List<object> Foo(IEnumerable<object> objects)
{
    if (objects == null)
        throw new ArgumentNullException("objects");

    var first = objects.FirstOrDefault();

    if (first == null)
        throw new ArgumentException(
            "Empty enumerable not supported.", 
            "objects");

    var list = DoSomeThing(first);  

    var secondList = DoSomeThingElse(objects);

    list.AddRange(secondList);

    return list;
}

Notez que cela suppose que vous IEnumerable n'est pas générique ou, au moins, est contraint d'être un type de référence.

-3voto

vidstige Points 4541

Tout d'abord, cet avertissement ne signifie pas toujours beaucoup. J'ai l'habitude de l'éteindre. Il veut simplement dire que l' IEnumerable est évalué deux fois, ce qui n'est généralement pas un problème, sauf si l' evaluation lui-même prend beaucoup de temps. Même si cela prend du temps, dans ce cas, votre seulement en utilisant un élément de la première fois autour.

Dans ce scénario, vous pourrait aussi exploiter la puissante extension linq méthodes encore plus.

var firstObject = objects.First();
return DoSomeThing(firstObject).Concat(DoSomeThingElse(objects).ToList();

Il est possible d'évaluer uniquement l' IEnumerable une fois dans ce cas, avec quelques soucis, mais le profil de la première et de voir si c'est vraiment un problème.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X