432 Stimmen

Behandlungswarnung bei möglicher Mehrfachaufzählung von IEnumerable

In meinem Code benötige ich eine IEnumerable<> mehrmals, was zu der ReSharper-Fehlermeldung "Mögliche mehrfache Aufzählung von IEnumerable ".

Beispiel-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;
}
  • Ich kann die objects Parameter zu sein List und dann die mögliche Mehrfachaufzählung vermeiden, aber dann bekomme ich nicht das höchste Objekt, das ich bearbeiten kann.
  • Eine weitere Möglichkeit ist die Konvertierung der IEnumerable まで List zu Beginn der Methode:

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

Aber das ist nur peinlich .

Was würden Sie in diesem Szenario tun?

557voto

Paul Stovell Punkte 31726

Das Problem bei der Einnahme von IEnumerable als Parameter ist, dass er dem Anrufer mitteilt: "Ich möchte dies aufzählen". Es sagt ihnen nicht, wie oft Sie die Aufzählung durchführen möchten.

Ich kann den Parameter objects in List ändern und so die mögliche mehrfache Aufzählung vermeiden, aber dann erhalte ich nicht das höchste Objekt, das ich bearbeiten kann .

Das Ziel, das höchste Objekt zu nehmen, ist edel, aber es lässt Raum für zu viele Annahmen. Wollen Sie wirklich, dass jemand eine LINQ to SQL-Abfrage an diese Methode übergibt, nur damit Sie sie zweimal aufzählen (und jedes Mal potenziell andere Ergebnisse erhalten)?

Der semantische Mangel hier ist, dass ein Aufrufer, der sich vielleicht nicht die Zeit nimmt, die Details der Methode zu lesen, annehmen könnte, dass Sie nur einmal iterieren - und Ihnen deshalb ein teures Objekt übergeben. Ihre Methodensignatur gibt keinen Hinweis auf eine der beiden Möglichkeiten.

Durch Änderung der Methodensignatur in IList / ICollection Damit machen Sie dem Anrufer zumindest klarer, was Sie von ihm erwarten, und er kann kostspielige Fehler vermeiden.

Andernfalls würden die meisten Entwickler, die sich die Methode ansehen, annehmen, dass Sie nur einmal iterieren. Wenn man eine IEnumerable so wichtig ist, sollten Sie in Erwägung ziehen, die .ToList() am Anfang der Methode.

Es ist schade, dass .NET keine Schnittstelle hat, die IEnumerable + Count + Indexer ist, ohne Add/Remove usw. Methoden, das ist, was ich vermute, würde dieses Problem lösen.

35voto

Marc Gravell Punkte 970173

Wenn Ihre Daten immer wiederholbar sein werden, brauchen Sie sich vielleicht keine Sorgen zu machen. Sie können sie jedoch auch abrollen - dies ist besonders nützlich, wenn die eingehenden Daten sehr groß sein könnten (z. B. beim Lesen von der Festplatte oder aus dem Netzwerk):

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

Beachten Sie, dass ich die Semantik von DoSomethingElse ein wenig geändert habe, aber dies dient hauptsächlich dazu, die unrollierte Verwendung zu zeigen. Man könnte zum Beispiel den Iterator neu wickeln. Man könnte ihn auch zu einem Iterator-Block machen, was schön wäre; dann gibt es keine list - und Sie würden yield return die Artikel, wenn Sie sie erhalten, anstatt sie zu einer Liste hinzuzufügen, die zurückgeschickt wird.

28voto

Gabriel Morin Punkte 528

Verwendung von IReadOnlyCollection<T> o IReadOnlyList<T> in der Methodensignatur anstelle von IEnumerable<T> hat den Vorteil, dass Sie explizit darauf hinweisen können, dass Sie die Zählung vor der Iteration überprüfen oder aus einem anderen Grund mehrfach iterieren müssen.

Sie haben jedoch einen großen Nachteil, der zu Problemen führt, wenn Sie versuchen, Ihren Code so umzugestalten, dass er Schnittstellen verwendet, z. B. um ihn besser testbar zu machen und dynamisches Proxying zu ermöglichen. Der springende Punkt ist, dass IList<T> erbt nicht von IReadOnlyList<T> und in ähnlicher Weise für andere Sammlungen und ihre jeweiligen schreibgeschützten Schnittstellen. (Kurz gesagt, liegt dies daran, dass .NET 4.5 die ABI-Kompatibilität mit früheren Versionen beibehalten wollte. Aber sie haben nicht einmal die Gelegenheit genutzt, das in .NET Core zu ändern. )

Das bedeutet, dass Sie, wenn Sie eine IList<T> von einem Teil des Programms und wollen es an einen anderen Teil weitergeben, der eine IReadOnlyList<T> können Sie nicht! Sie können jedoch eine IList<T> como IEnumerable<T> .

Zum Schluss, IEnumerable<T> ist die einzige Nur-Lese-Schnittstelle, die von allen .NET-Sammlungen einschließlich aller Sammlungsschnittstellen unterstützt wird. Jede andere Alternative wird Ihnen zum Verhängnis, wenn Sie feststellen, dass Sie sich selbst von einigen Architekturentscheidungen ausgeschlossen haben. Daher denke ich, dass es der richtige Typ ist, um in Funktionssignaturen auszudrücken, dass Sie nur eine schreibgeschützte Sammlung wollen.

(Beachten Sie, dass Sie jederzeit eine IReadOnlyList<T> ToReadOnly<T>(this IList<T> list) Erweiterungsmethode, die einfach castet, wenn der zugrundeliegende Typ beide Schnittstellen unterstützt, aber Sie müssen sie beim Refactoring überall manuell hinzufügen, während IEnumerable<T> ist immer kompatibel.)

Wie immer ist dies kein absoluter Wert. Wenn Sie datenbanklastigen Code schreiben, bei dem eine versehentliche Mehrfachaufzählung eine Katastrophe wäre, werden Sie vielleicht einen anderen Kompromiss vorziehen.

11voto

Nishan Punkte 2686

Mit .NET 6/ C# 10

und darüber hinaus können Sie versuchen, die Anzahl der Elemente in einer Folge zu bestimmen, ohne eine Aufzählung zu erzwingen, indem Sie Enumerable.TryGetNonEnumeratedCount(IEnumerable, Int32) méthode.

Diese Methode gibt zurück true wenn die Anzahl der source ohne Aufzählung bestimmt werden kann; ansonsten, false . So können Sie prüfen, ob eine weitere Implementierung erforderlich ist.

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main()
    {
        IEnumerable<int> arrayOne = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

        var canGetCountDirectly = arrayOne.TryGetNonEnumeratedCount(out int theCount);

        Console.WriteLine($"Count can be returned directly = {canGetCountDirectly}");
        Console.WriteLine($"Count = {theCount}");
    }
}

2voto

João Angelo Punkte 54226

Wenn das Ziel ist wirklich zu verhindern, dass mehrere Aufzählungen als die Antwort von Marc Gravell ist die zu lesen, aber die gleiche Semantik könnten Sie einfach entfernen Sie die redundante Any y First anrufen und mitgehen:

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

Beachten Sie, dass dies voraussetzt, dass Sie IEnumerable nicht generisch ist oder zumindest auf einen Referenztyp beschränkt ist.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X