Peter Bucher - Mein Experiment, meine Spielereien, meine Welt...   ·   Stefan Falz   ·   Jürgen Gutsch   ·   Golo Roden   ·   ASP.NET Zone   ·   Microsoft ASP.NET
Willkommen bei ASP.NET Zone. Anmelden | Registrieren | Hilfe

Wann IEnumerable nutzen, wann ICollection und wieso überhaupt IList?

Es gibt einige Mengen- /  Auflistungstypen in .NET.

Ein kleine Auswahl:

Früher gab es nur nicht generische bzw. keine stark typisierte Collections, die heute nicht mehr verwendet werden sollten,
da immer explizit gecastet werden muss, was umständlich ist und Performance kostet.

Hierfür zitiere ich gerne herbivore aus myCSharp:

ArrayList gehört in die Mottenkiste und sollte wie alle untypisierten Collections aus System.Collections nicht mehr benutzt werden. Verwende stattdessen List<T> und alle anderen typisierten Collections aus System.Collections.Generic.

Ich möchte hier allerdings nicht die verschiedenen Auflistungstypen im Detail anschauen, sondern herausfinden, wann welcher konkreter Typ oder gar das Interface in einer Signatur verwendet werden sollte.

Ein kleines Beispiel:


public void PrintNamesToConsole(List<string> names)
{
    foreach(string name in names)
    {
        Console.WriteLine(name);
    }
}

Dieser Code verwendet List<T> als Argumenttyp, es besteht also eine Abhängigkeit darauf,
d.h. jeder Aufrufer muss hier eine List<T> übergeben, alles andere funktioniert nicht.
Beispielsweise kann hier kein Array übergeben werden, oder eine Collection.

Wenn dann innerhalb der Methode, die das Argument entgegennimmt, nicht einmal
auf spezifische Methoden / Eigenschaften des konkreten Types (List<T>) zugegriffen wird,
ist das noch ein grösser Nachteil, denn man erkauft sich eine Abhängigkeit, ohne das man sie eigentlich benötigt.

Hört sich komisch an?
Eigentlich ist es relativ leicht, wenn wir uns mal die verschiedenen Schnittstellen betrachten, worauf die Auflistungen in .NET aufbauen.

Der eigentliche Kern ist die Schnittstelle IEnumerable bzw. der typisierte Bruder IEnumerable<T>, wobei dort T den Typ der enthaltenen Elemente angibt.

IEnumerable:

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

IEnumerable<T>:

public interface IEnumerable<T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}

Das Interface IEnumerator sieht dann so aus:
(Die generische Implementation IEnumerator<T>, hat zusätzlich eine Eigenschaft vom Typ T über die Eigenschaft Current).


public interface IEnumerator
{
    bool MoveNext();
    object Current { get; }
    void Reset();
}

Anhand der Beispiele ist zu sehen, das IEnumerable / IEnumerable<T> und die dazugehörige Schnittstelle für den
Enumerator beschreibt, das Elemente durchlaufen werden können. Nicht mehr und nicht weniger.

Das Beispiel von oben, könnte wie folgt umgeschrieben werden, um die kleinste Abhängigkeit zu haben und trotzdem
alle Anforderungen funktionieren, denn es muss ja nur eine Auflistung durchlaufen werden, nicht mehr und nicht weniger.

public void PrintNamesToConsole(IEnumerable<string> names)
{
    foreach(string name in names)
    {
        Console.WriteLine(name);
    }
}

Man beachte den geänderten Argumenttyp, der jetzt viel genereller ist.
Jetzt kann die Methode ein string[], eine List<T>, … entgegennehmen, einfach alles das die Schnittstelle IEnumerable<T> implementiert.

Wird eine Unterstützung in der Art von <Auflistung>.Count gefordert, kann die Schnittstelle ICollection<T> angegeben werden, die eine solche Eigenschaft nativ mitbringt.
Zusätzlich beschreibt das Interface (Erst ab der generischen Variante), das ein Element hinzugefügt, die Auflistung geleert, zurückgeben kann ob ein Element exisitert und Elemente entfernt werden können.

Durch LINQ to Objects können praktisch alle Operationen, wie bspw. Anzahl Elemente abfragen, sortieren, filtern, Zugriff über Index, damit und auf dem IEnumerable<T> Typen gemacht werden.
LINQ to Objects ist schnell, sehr schnell sogar. Wird jedoch die höchste Leistung benötigt und bspw. sehr häufig auf die .Count-Eigenschaft zugegriffen,
ist es besser, wenn eine native Unterstützung einer solchen Eigenschaft vorliegt.

LINQ to Objects arbeitet bei Count ungefähr so:

  • Versuche die Auflistung nach ICollection<T> zu casten.
  • Wenn das klappt, rufe die .Count-Eigenschaft ab und gib das Ergebnis zurück.
  • Wenn es nicht klappt, durchlaufe alle Einträge in der Auflistung, zähle die Anzahl zusammen und gib das Ergebnis zurück.

Das Interface IList<T> selber implementiert ICollection<T> – kann also alles auch – jedoch gibt es zusätzlich noch die Möglichkeit nativ per Index zu arbeiten. Also Elemente indexiert abfragen, ein Element an einem bestimmten Index löschen / einfügen.

In den meisten Fällen reicht es also, wenn IEnumerable<T> als Argumenttyp bzw. Rückgabetyp angegeben wird und in den anderen Fällen jeweils das Interface ICollection<T> / IList<T>, je nachdem was benötigt wird.

Es ist zu beachten, das IList<T> nicht alles beschreibt, was List<T> implementiert, bspw. gibt es dann keine Find- / FindAll-Methode, sowie auch keine native Implementierung für das Sortieren.
Mit LINQ to Objects tritt das allerdings in den Hintergrund und so kann man sich im Code von Abhängigkeiten zu den konkreten Auflistungstypen lösen.

Wieso soll man sich überhaupt lösen?

Umso genereller die Argument- oder Rückgabetypen sind, desto flexibler ist die API und es wird nicht mehr öffentlich gemacht, als schlussendlich verwendet wird.
Zusätzlich ist die Abhängigkeit zu einem konkreten Typ weg, was bedeutet das bspw. anstelle einer List<T> einfach irgend eine andere Implementierung von IEnumerable<T> empfangen / zurückgegeben werden kann.

So kann bspw. später eine Auflistung implementiert werden, die bei einem bestimmten Ereignis wie bspw. das Entfernen eines Eintrages, einen Event auslöst, ohne den Argumenttyp zu ändern.

Folgend noch ein paar sehr interessante Beiträge zum Thema:

Veröffentlicht Donnerstag, 19. November 2009 18:09 von Peter Bucher

Kommentare

# Twitter Trackbacks for Peter Bucher : Wann IEnumerable nutzen, wann ICollection und wieso ??berhaupt IList? [aspnetzone.de] on Topsy.com

# C# &#8211; Wissenwertes zu IEnumerable und wie man es richtig nutzt

Peter Bucher hat einen interessanten Artikel rund um IEnumerable und wie und wann man es richtig nutzt veröffentlicht.

Sollte man mal gelesen haben und natürlich auch anwenden. Ich werd es auf jeden Fall!

Wann IEnumerable nutzen, wann ICollection und

Donnerstag, 19. November 2009 19:43 by welt-held.de

# re: Wann IEnumerable nutzen, wann ICollection und wieso überhaupt IList?

Schöner Artikel Peter :)

Kleine Anmerkung :

"Desto genereller die Argument- oder Rückgabetypen sind, desto flexibler ist die API und ..."

Am besten mit "je" oder "umso" anfangen oder :) ? Klingt ein wenig komisch so.

Donnerstag, 19. November 2009 20:27 by KroaX

# re: Wann IEnumerable nutzen, wann ICollection und wieso überhaupt IList?

Hey Peter,

vielen Dank für den Artikel! :)

Beste Grüße,

Gregor

Donnerstag, 19. November 2009 21:58 by BFreakout

# re: Wann IEnumerable nutzen, wann ICollection und wieso überhaupt IList?

Hallo Peter,

super erklärt, vielen Dank :)

Freitag, 20. November 2009 13:53 by Roberto

# re: Wann IEnumerable nutzen, wann ICollection und wieso überhaupt IList?

Danke für den Artikel, echt super erklärt!

Montag, 18. Januar 2010 11:04 by Youporn

# re: Wann IEnumerable nutzen, wann ICollection und wieso überhaupt IList?

An sich finde ich es gut, bei dem was man macht so abstrakt wie möglich zu bleiben, möchte aber noch auf einen Fallstrick bei der Verwendung von IEnumerable hinweisen:

Wenn man über die IEnumerable<T> mit foreach iteriert wird für jede Iteration IEnumerator<T>.MoveNext() aufgerufen. Das tut an sich erst einmal nicht weh, aber der Overhead kann bei großen Listen und Verschachtelten Iterationen sehr in die Laufzeit gehen. Ich hatte gerade ein Beispiel, bei dem eine Analyse, bei der foreach (2 Ebenen) verwendet wurde, die insgesamt eine Laufzeit von ca. 30 bis 45 Minuten hatte. Mit der Umstellung auf klassische Arrays und for-Schleifen konnte ich eine Reduktion der Laufzeit auf ca. 2 Minuten (!) erreichen, somit eine Beschleunigung um den Faktor 15-20. Wie gesagt bin ich immer für den Einsatz von Abstraktionen, aber man muss eben wissen was man damit macht.

-Paul

Freitag, 4. September 2015 13:08 by less0

# re: Wann IEnumerable nutzen, wann ICollection und wieso überhaupt IList?

@Paul: ist bestimmt ein Interpretationsfehler des Codeverhaltens - es gibt seitens des .Net Frameworks gar nicht so große Listen, daß deren Durchlaufen so viel Abarbeitungszeit schlucken würden. Selbst bei maximaler Listengröße dauert das reine Iterieren weniger als 1s, bei der ListenKlasse wie auch dem Interface.

Dein Zeitfresser muß also anderswo im Code stecken.

Montag, 7. September 2015 14:58 by Micke
Anonyme Kommentare sind nicht zugelassen