Sollte ich immer IEnumerable <T> anstelle von IList <T> zurückgeben?

97

Wenn ich meine DAL oder einen anderen Code schreibe, der eine Reihe von Elementen zurückgibt, sollte ich immer meine return-Erklärung abgeben:

public IEnumerable<FooBar> GetRecentItems()

oder

public IList<FooBar> GetRecentItems()

Derzeit habe ich in meinem Code versucht, IEnumerable so oft wie möglich zu verwenden, bin mir aber nicht sicher, ob dies die beste Vorgehensweise ist? Es schien richtig zu sein, weil ich den allgemeinsten Datentyp zurückgab, während ich immer noch beschrieb, was er tut, aber vielleicht ist dies nicht richtig.

KingNestor
quelle
1
Ist es List <T> oder IList <T>, nach der Sie fragen? Der Titel und die Frage sagen verschiedene Dinge ...
Fredrik Mörk
Wann immer möglich Benutzeroberfläche IEnumerable oder Ilist instaead vom Typ Concerete.
Usman Masood
2
Ich gebe Sammlungen als Liste <T> zurück. Ich sehe keine Notwendigkeit, IEnumberable <T> zurückzugeben, da Sie dies aus List <T>
Chuck Conway
Mögliches Duplikat von ienumerablet-as-return-type
nawfal

Antworten:

44

Es hängt wirklich davon ab, warum Sie diese spezielle Schnittstelle verwenden.

Hat zum Beispiel IList<T>mehrere Methoden, die in nicht vorhanden sind IEnumerable<T>:

  • IndexOf(T item)
  • Insert(int index, T item)
  • RemoveAt(int index)

und Eigenschaften:

  • T this[int index] { get; set; }

Wenn Sie diese Methoden in irgendeiner Weise benötigen, kehren Sie auf jeden Fall zurück IList<T>.

Wenn die Methode, die Ihr IEnumerable<T>Ergebnis verwendet, eine erwartet IList<T>, wird die CLR vor der Berücksichtigung der erforderlichen Konvertierungen bewahrt, wodurch der kompilierte Code optimiert wird.

Jon Limjap
quelle
2
@ Jon FDG empfiehlt die Verwendung von Collection <T> oder ReadOnlyCollection <T> als Rückgabewert für Collection-Typen. Siehe meine Antwort.
Sam Saffron
Sie sind sich nicht sicher, was Sie in Ihrem letzten Satz meinen, wenn Sie sagen "es wird die CLR retten". Was wird es mit IEnumerable vs. IList speichern? Können Sie das klarer machen?
PositiveGuy
1
@CoffeeAddict Drei Jahre nach dieser Antwort denke ich, dass Sie Recht haben - dieser letzte Teil ist vage. Wenn eine Methode, die eine IList <T> als Parameter erwartet, eine IEnumerable <T> erhält, muss die IEnumerable manuell in einen neuen List <T> oder einen anderen IList <T> -Implementierer eingeschlossen werden, und diese Arbeit wird von nicht ausgeführt die CLR für Sie. Das Gegenteil - eine Methode, die erwartet, dass ein IEnumerable <T> eine IList <T> erhält, muss möglicherweise etwas Unboxing durchführen, muss dies jedoch im Nachhinein möglicherweise nicht, da IList <T> IEnumerable <T> implementiert.
Jon Limjap
68

Richtlinien für das Framework-Design empfehlen die Verwendung der Klasse Collection, wenn Sie eine Collection zurückgeben müssen, die vom Aufrufer oder ReadOnlyCollection für schreibgeschützte Sammlungen geändert werden kann.

Der Grund, warum dies einem einfachen vorgezogen wird, IListist, dass IListder Anrufer nicht informiert wird, ob er schreibgeschützt ist oder nicht.

Wenn Sie IEnumerable<T>stattdessen eine zurückgeben, kann es für den Anrufer etwas schwieriger sein, bestimmte Vorgänge auszuführen. Außerdem geben Sie dem Anrufer nicht mehr die Flexibilität, die Sammlung zu ändern, was Sie möglicherweise möchten oder nicht.

Beachten Sie, dass LINQ einige Tricks enthält und bestimmte Anrufe basierend auf dem Typ, für den sie ausgeführt werden, optimiert. Wenn Sie beispielsweise eine ausführen Countund die zugrunde liegende Sammlung eine Liste ist, werden NICHT alle Elemente durchlaufen.

Persönlich würde ich mich für ein ORM wahrscheinlich an Collection<T>meinen Rückgabewert halten.

Sam Safran
quelle
12
Die Richtlinien für Sammlungen enthalten eine detailliertere Liste von DOs und DONTs.
Chaquotay
26

Im Allgemeinen sollten Sie das allgemeinste benötigen und das spezifischste zurückgeben, das Sie können. Wenn Sie also eine Methode haben, die einen Parameter akzeptiert, und nur das benötigen, was in IEnumerable verfügbar ist, sollte dies Ihr Parametertyp sein. Wenn Ihre Methode entweder eine IList oder eine IEnumerable zurückgeben könnte, ziehen Sie es vor, IList zurückzugeben. Dies stellt sicher, dass es für die unterschiedlichsten Verbraucher nutzbar ist.

Seien Sie locker in dem, was Sie benötigen, und explizit in dem, was Sie bereitstellen.

Mel
quelle
1
Ich bin zu dem gegenteiligen Schluss gekommen: Man sollte bestimmte Typen akzeptieren und allgemeine Typen zurückgeben. Sie können jedoch Recht haben, dass allgemein anwendbare Methoden besser sind als eingeschränktere Methoden. Ich werde mehr darüber nachdenken müssen.
Dave Cousineau
3
Die Rechtfertigung für das Akzeptieren allgemeiner Typen als Eingabe besteht darin, dass Sie mit einem möglichst breiten Eingabebereich arbeiten können, um eine Komponente so weit wie möglich wiederzuverwenden. Da Sie jedoch bereits genau wissen, welche Art von Objekt Sie zur Hand haben, macht es nicht viel Sinn, es zu maskieren.
Mel
1
Ich glaube, ich bin damit einverstanden, allgemeinere Parameter zu haben, aber was ist Ihre Begründung für die Rückgabe von etwas weniger Allgemeinem?
Dave Cousineau
6
Okay, lass mich das anders versuchen. Warum sollten Sie Informationen wegwerfen? Wenn Sie sich nur dafür interessieren, dass das Ergebnis IEnumerable <T> ist, tut es Ihnen dann irgendwie weh zu wissen, dass es sich um eine IList <T> handelt? Nein, das tut es nicht. In einigen Fällen kann es sich um überflüssige Informationen handeln, die Ihnen jedoch keinen Schaden zufügen. Nun zum Vorteil. Wenn Sie eine Liste oder IList zurückgeben, kann ich sofort feststellen, dass die Sammlung bereits abgerufen wurde, was ich mit einer IEnumerable nicht wissen kann. Dies kann eine nützliche Information sein oder auch nicht, aber warum sollten Sie noch einmal Informationen wegwerfen? Wenn Sie zusätzliche Informationen zu etwas wissen, geben Sie diese weiter.
Mel
Rückblickend bin ich überrascht, dass mich noch nie jemand angerufen hat. Eine IEnumerable WURDE bereits abgerufen. Ein IQueryable kann jedoch in einem nicht aufgezählten Zustand zurückgegeben werden. Ein besseres Beispiel wäre also vielleicht IQueryable vs IEnumerable zurückzugeben. Die Rückgabe des spezifischeren IQueryable würde eine weitere Komposition ermöglichen, während die Rückgabe von IEnumerable eine sofortige Aufzählung erzwingen und die Kompositionsfähigkeit der Methode verringern würde.
Mel
23

Kommt darauf an...

Wenn Sie den am wenigsten abgeleiteten Typ ( IEnumerable) zurückgeben, haben Sie den größten Spielraum, um die zugrunde liegende Implementierung später zu ändern.

Durch die Rückgabe eines stärker abgeleiteten Typs ( IList) erhalten die Benutzer Ihrer API mehr Operationen für das Ergebnis.

Ich würde immer empfehlen, den am wenigsten abgeleiteten Typ zurückzugeben, der alle Operationen enthält, die Ihre Benutzer benötigen. Grundsätzlich müssen Sie also zunächst festlegen, welche Operationen für das Ergebnis im Kontext der von Ihnen definierten API sinnvoll sind.

jerryjvl
quelle
nette generische Antwort, da sie auch für andere Methoden gilt.
user420667
1
Ich weiß, dass diese Antwort sehr alt ist ... aber sie scheint der Dokumentation zu widersprechen: "Wenn weder die IDictionary <TKey, TValue> -Schnittstelle noch die IList <T> -Schnittstelle die Anforderungen der erforderlichen Sammlung erfüllen, leiten Sie die neue Sammlungsklasse ab stattdessen die ICollection <T> -Schnittstelle für mehr Flexibilität "Dies scheint zu implizieren, dass der stärker abgeleitete Typ bevorzugt werden sollte. ( msdn.microsoft.com/en-us/library/92t2ye13(v=vs.110).aspx ) \
DeborahK
11

Wenn Sie eine LINQ-Anweisung mit verzögerter Ausführung verwenden, um Ihre zu generieren IEnumerable<T>, bedeutet ein Aufruf, .ToList()bevor Sie von Ihrer Methode zurückkehren, dass Ihre Elemente möglicherweise zweimal wiederholt werden - einmal, um die Liste zu erstellen, und einmal, wenn der Aufrufer eine Schleife durchläuft , filtert oder transformiert Ihren Rückgabewert. Wenn es praktisch ist, möchte ich vermeiden, die Ergebnisse von LINQ-to-Objects in eine konkrete Liste oder ein konkretes Wörterbuch zu konvertieren, bis ich muss. Wenn mein Anrufer eine Liste benötigt, ist dies nur ein einfacher Methodenaufruf - ich muss diese Entscheidung nicht für ihn treffen, und das macht meinen Code in den Fällen, in denen der Anrufer nur einen Foreach ausführt, etwas effizienter.

Joel Mueller
quelle
@ Joel Mueller, ich würde normalerweise sowieso ToList () aufrufen. Ich mag es im Allgemeinen nicht, IQueryable dem Rest meiner Projekte auszusetzen.
KingNestor
4
Ich bezog mich eher auf LINQ-to-Objects, bei denen IQueryable im Allgemeinen nicht ins Bild kommt. Wenn eine Datenbank beteiligt ist, wird ToList () notwendiger, da Sie sonst riskieren, Ihre Verbindung vor dem Iterieren zu schließen, was nicht sehr gut funktioniert. Wenn dies jedoch kein Problem darstellt, ist es ziemlich einfach, IQueryable als IEnumerable verfügbar zu machen, ohne eine zusätzliche Iteration zu erzwingen, wenn Sie IQueryable ausblenden möchten.
Joel Mueller
9

List<T>bietet dem aufrufenden Code viele weitere Funktionen, z. B. das Ändern des zurückgegebenen Objekts und den Zugriff über den Index. Die Frage lautet also: Wollen Sie im spezifischen Anwendungsfall Ihrer Anwendung solche Verwendungen unterstützen (vermutlich durch Rückgabe einer frisch erstellten Sammlung!), Um es dem Anrufer bequem zu machen - oder möchten Sie Geschwindigkeit für den einfachen Fall, wenn alle Der Anrufer muss die Sammlung durchlaufen und Sie können sicher einen Verweis auf eine echte zugrunde liegende Sammlung zurückgeben, ohne befürchten zu müssen, dass diese fälschlicherweise geändert wird usw.?

Nur Sie können diese Frage beantworten, und nur wenn Sie genau wissen, was Ihre Anrufer mit dem Rückgabewert tun möchten und wie wichtig die Leistung hier ist (wie groß sind die Sammlungen, die Sie kopieren würden, wie wahrscheinlich ist dies ein Engpass). etc).

Alex Martelli
quelle
"Geben Sie einen Verweis auf eine echte zugrunde liegende Sammlung sicher zurück, ohne zu befürchten, dass er fälschlicherweise geändert wird." - Selbst wenn Sie IEnumerable <T> zurückgeben, können sie ihn nicht einfach in eine Liste <T> zurücksetzen und ändern?
Kobi
Nicht jede IEnumarable <T> ist auch eine Liste <T>. Wenn das zurückgegebene Objekt nicht von einem Typ ist, der von List <T> erbt oder IList <T> implementiert, würde dies zu einer InvalidCastException führen.
Lowglider
2
List <T> hat das Problem, dass es Sie an eine bestimmte Implementierung bindet. Collection <T> oder ReadOnlyCollection <T> werden bevorzugt
Sam Saffron
4

Ich denke, Sie können beide verwenden, aber jeder hat eine Verwendung. Grundsätzlich Listist IEnumerableaber Sie haben Zählfunktion, Element hinzufügen, Element entfernen

IEnumerable ist zum Zählen von Elementen nicht effizient

Wenn die Sammlung schreibgeschützt sein soll oder die Änderung der Sammlung von der gesteuert wird, ist es keine gute Idee, Parentein IListJust for zurückzugeben Count.

In Linq gibt es eine Count()Erweiterungsmethode, bei IEnumerable<T>der innerhalb der CLR eine Verknüpfung hergestellt wird, .Countwenn der zugrunde liegende Typ vorliegt IList, sodass der Leistungsunterschied vernachlässigbar ist.

Im Allgemeinen halte ich es für eine bessere Praxis, IEnumerable zurückzugeben, wenn dies erforderlich ist. Wenn Sie Ergänzungen vornehmen müssen, fügen Sie diese Methoden der übergeordneten Klasse hinzu. Andernfalls verwaltet der Verbraucher die Sammlung innerhalb von Model, was gegen die Prinzipien manufacturer.Models.Add(model)verstößt , z. B. gegen das Gesetz von demeter. Natürlich sind dies nur Richtlinien und keine festen Regeln, aber bis Sie die Anwendbarkeit vollständig verstanden haben, ist es besser, blind zu folgen, als überhaupt nicht zu folgen.

public interface IManufacturer 
{
     IEnumerable<Model> Models {get;}
     void AddModel(Model model);
}

(Hinweis: Wenn Sie nNHibernate verwenden, müssen Sie möglicherweise eine private IList mit verschiedenen Accessoren zuordnen.)

SO Benutzer
quelle
2

Es ist nicht so einfach, wenn Sie von Rückgabewerten anstelle von Eingabeparametern sprechen. Wenn es sich um einen Eingabeparameter handelt, wissen Sie genau, was Sie tun müssen. Wenn Sie also in der Lage sein müssen, über die Sammlung zu iterieren, verwenden Sie eine IE-Nummer, während Sie beim Hinzufügen oder Entfernen eine IList verwenden.

Bei einem Rückgabewert ist es schwieriger. Was erwartet Ihr Anrufer? Wenn Sie eine IEnumerable zurückgeben, weiß er a priori nicht, dass er daraus eine IList machen kann. Wenn Sie jedoch einen IList zurückgeben, weiß er, dass er darüber iterieren kann. Sie müssen also berücksichtigen, was Ihr Anrufer mit den Daten tun wird. Die Funktionalität, die Ihr Anrufer benötigt / erwartet, sollte bei der Entscheidung über die Rückgabe maßgeblich sein.

JP Alioto
quelle
0

Wie alle gesagt haben, hängt es davon ab, ob Sie keine Funktion zum Hinzufügen / Entfernen auf der aufrufenden Ebene hinzufügen möchten, dann werde ich für IEnumerable stimmen, da es nur Iteration und grundlegende Funktionen bietet, die mir in der Design-Perspektive gefallen. Zurück IList meine Stimmen sind immer wieder dagegen, aber es ist hauptsächlich das, was du magst und was nicht. In Bezug auf die Leistung denke ich, dass sie mehr gleich sind.

Usman Masood
quelle
0

Wenn Sie in Ihrem externen Code nicht zählen, ist es immer besser, IEnumerable zurückzugeben, da Sie später Ihre Implementierung ändern können (ohne Auswirkungen auf den externen Code), um beispielsweise Iteratorlogik zu erzielen und Speicherressourcen zu sparen (übrigens sehr gute Sprachfunktion) ).

Wenn Sie jedoch die Anzahl der Elemente benötigen, vergessen Sie nicht, dass zwischen IEnumerable und IList - ICollection eine weitere Ebene liegt .

Schiedsrichter
quelle
0

Ich bin vielleicht ein bisschen daneben, da niemand es bisher vorgeschlagen hat, aber warum gibst du keine zurück (I)Collection<T>?

Soweit ich mich erinnere, Collection<T>war der bevorzugte Rückgabetyp dem, List<T>weil er die Implementierung abstrahiert. Sie alle implementieren IEnumerable, aber das klingt für mich etwas zu niedrig für den Job.

Tiberiu Ana
quelle
0

Ich denke, Sie können beide verwenden, aber jeder hat eine Verwendung. Grundsätzlich Listist IEnumerableaber Sie haben Zählfunktion, Element hinzufügen, Element entfernen

IEnumerable ist nicht effizient zum Zählen von Elementen oder zum Abrufen eines bestimmten Elements in der Sammlung.

List ist eine Sammlung, die sich ideal zum Auffinden bestimmter Elemente, zum einfachen Hinzufügen oder Entfernen von Elementen eignet.

Im Allgemeinen versuche ich, Listwenn möglich zu verwenden , da dies mir mehr Flexibilität gibt.

Verwenden Sie List<FooBar> getRecentItems() eher als IList<FooBar> GetRecentItems()

Stuart
quelle
0

Ich denke, die allgemeine Regel ist, die spezifischere Klasse zu verwenden, um zurückzukehren, unnötige Arbeit zu vermeiden und Ihrem Anrufer mehr Optionen zu geben.

Trotzdem denke ich, dass es wichtiger ist, den Code vor Ihnen zu berücksichtigen, den Sie schreiben, als den Code, den der nächste Typ schreiben wird (im Rahmen der Vernunft). Dies liegt daran, dass Sie Annahmen über den bereits vorhandenen Code treffen können.

Denken Sie daran, dass das Verschieben nach UP zu einer Sammlung von IEnumerable in einer Schnittstelle funktioniert. Wenn Sie von einer Sammlung nach IEnumerable wechseln, wird der vorhandene Code beschädigt.

Wenn diese Meinungen alle widersprüchlich erscheinen, liegt dies daran, dass die Entscheidung subjektiv ist.

Sprague
quelle
0

TL; DR; - Zusammenfassung

  • Wenn Sie interne Software entwickeln, verwenden Sie den spezifischen Typ (Like List) für die Rückgabewerte und den allgemeinsten Typ für Eingabeparameter, auch bei Sammlungen.
  • Wenn eine Methode Teil der öffentlichen API einer weiterverteilbaren Bibliothek ist, verwenden Sie Schnittstellen anstelle konkreter Auflistungstypen, um sowohl Rückgabewerte als auch Eingabeparameter einzuführen.
  • Wenn eine Methode eine schreibgeschützte Auflistung zurückgibt, zeigen Sie dies, indem Sie IReadOnlyListoder IReadOnlyCollectionals Rückgabewerttyp verwenden.

Mehr

Ali Bayat
quelle