Unterschied zwischen IEnumerable Count () und Length

69

Was sind die Hauptunterschiede zwischen IEnumerable Count()und Length?

Balalakshmi
quelle

Antworten:

102

Wenn Sie Count on aufrufen IEnumerable<T>, gehen Sie davon aus, dass Sie sich auf die Erweiterungsmethode Counton beziehen System.Linq.Enumerable. Lengthist keine Methode für IEnumerable<T>, sondern eine Eigenschaft für Array-Typen in .Net wie int[].

Der Unterschied ist die Leistung. Bei der LengthImmobilie handelt es sich garantiert um eine O (1) -Operation. Die Komplexität der CountErweiterungsmethode hängt vom Laufzeittyp des Objekts ab. Es wird versucht, mehrere Typen zu verwenden, die die Suche nach O (1) -Längen wie ICollection<T>über eine CountEigenschaft unterstützen. Wenn keine verfügbar sind, werden alle Elemente aufgelistet und gezählt, die eine Komplexität von O (N) haben.

Zum Beispiel

int[] list = CreateSomeList();
Console.WriteLine(list.Length);  // O(1)
IEnumerable<int> e1 = list;
Console.WriteLine(e1.Count()); // O(1) 
IEnumerable<int> e2 = list.Where(x => x <> 42);
Console.WriteLine(e2.Count()); // O(N)

Der Wert e2wird als C # -Iterator implementiert, der die O (1) -Zählung nicht unterstützt. Daher Countmuss die Methode die gesamte Sammlung auflisten, um zu bestimmen, wie lange sie dauert .

JaredPar
quelle
6
List<T>hat keine Length-Eigenschaft - es hat eine Count-Eigenschaft. Arrays haben eine Lengthobwohl. Countwird in ICollectionund ICollection<T>(die IList<T>erweitert) angegeben.
Jon Skeet
@JonSkeet und @Jared - Im Zusammenhang mit dem Parsen eines kurzen string[]Arrays mit beispielsweise 5-10 Elementen ... würden Sie dann eine Array.LengthLeistung vorschlagen ?
one.beat.consumer
1
@ one.beat.consumer: Es wäre immer noch O (1) mit, Count()da das Array implementiert wird ICollection<T>- aber es ist weniger effizient als die Lengthdirekte Verwendung . Wenn Sie bereits wissen, dass es sich um ein Array handelt, würde ich es Length nicht aus Effizienzgründen verwenden, sondern weil ich es als idiomatischer ansehen würde. Ebenso würde ich die CountEigenschaft für alles verwenden, was einen Typ zur Kompilierungszeit hatte ICollection<T>. Ich würde anrufen, Count()wenn der Ausdruck zur Kompilierungszeit vom Typ ist IEnumerable<T>, auch wenn ich weiß, dass es sich wirklich um ein Array hinter den Kulissen handelt.
Jon Skeet
@ JonSkeet: Danke. Meine (dumme) Annahme war, dass dies System.Arrayeine viel grundlegendere Klasse war (ähnlich wie object); Nach dem Überprüfen von MSDN werden jedoch mehrere "sammlungsbezogene" Schnittstellen implementiert. Ich bin ein Web - Programmierer so „Count“ gemacht semantischen Sinne (wie headervs. divin HTML) auf den ersten, aber ich verstehe Ihren Standpunkt , da sie explizit eine ist string[]zur Compile-Zeit Lengthmehr Sinn macht.
one.beat.consumer
Hallo JaredPar, können Sie bitte erklären, ob die Komplexität für die Liste immer noch O (1) ist, wenn ich diese Liste innerhalb einer Schleife ändern werde? ZB while(list.Count() > 0)einige Logiken mit Aktionen zum Hinzufügen / Entfernen.
Johnny_D
24

Kleine Ergänzung zu Jon Skeets Kommentar.

Hier ist der Quellcode der Count()Erweiterungsmethode:

.NET 3:

public static int Count<TSource>(this IEnumerable<TSource> source)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    ICollection<TSource> is2 = source as ICollection<TSource>;
    if (is2 != null)
    {
        return is2.Count;
    }
    int num = 0;
    using (IEnumerator<TSource> enumerator = source.GetEnumerator())
    {
        while (enumerator.MoveNext())
        {
            num++;
        }
    }
    return num;
}

.NET 4:

public static int Count<TSource>(this IEnumerable<TSource> source)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    ICollection<TSource> is2 = source as ICollection<TSource>;
    if (is2 != null)
    {
        return is2.Count;
    }
    ICollection is3 = source as ICollection;
    if (is3 != null)
    {
        return is3.Count;
    }
    int num = 0;
    using (IEnumerator<TSource> enumerator = source.GetEnumerator())
    {
        while (enumerator.MoveNext())
        {
            num++;
        }
    }
    return num;
}
bniwredyc
quelle
9
Beachten Sie, dass in .NET 4 ein weiterer Block vorhanden ist, um auch nach dem nicht generischen ICollectionTyp zu suchen. (Da das auch eine CountEigenschaft hat.)
Jon Skeet
Weiß jemand, wie useman die ErrorKlasse erhält, die diese Methode verwendet? Ich kann es anscheinend nirgendwo auf MSDN finden, außer in der JScript-Dokumentation.
Moshe Katz
2
  • Die Länge ist eine feste Eigenschaft, z. B. eines eindimensionalen Arrays oder Strings. Es ist also nie eine Zähloperation erforderlich (mehrdimensionale Arrays haben eine Größe aller Dimensionen multipliziert). O (1) -Operation bedeutet hier, dass die Abrufzeit immer gleich ist, egal wie viele Elemente es gibt. Eine lineare Suche wäre (im Gegensatz dazu) O (n).

  • Die Count-Eigenschaft für ICollections (z. B. List und List <T>) kann sich ändern. Sie muss daher entweder beim Hinzufügen / Entfernen aktualisiert werden oder wenn Count angefordert wird, nachdem die Collection geändert wurde. Hängt von der Implementierung des Objekts ab.

  • Die Count () -Methode von LINQ iteriert grundsätzlich JEDES MAL, wenn sie aufgerufen wird (außer wenn das Objekt ein ICollection-Typ ist, wird die ICollection.Count-Eigenschaft angefordert).

Beachten Sie, dass IEnumerables häufig nicht bereits definierte Objektsammlungen (wie Listen, Arrays, Hashtabellen usw.) sind, sondern mit Hintergrundoperationen verknüpft sind, die bei jeder Anforderung Ergebnisse generieren (als verzögerte Ausführung bezeichnet).

Normalerweise haben Sie eine SQL-ähnliche LINQ-Anweisung wie diese (die typische Anwendung der verzögerten Ausführung):

IEnumerable<Person> deptLeaders = 
   from p in persons
   join d in departments
      on p.ID equals d.LeaderID
   orderby p.LastName, p.FirstName
   select p;

Dann gibt es Code wie diesen:

if (deptLeaders.Count() > 0)
{
   ReportNumberOfDeptLeaders(deptLeaders.Count());
   if (deptLeaders.Count() > 20)
      WarnTooManyDepartmentLeaders(deptLeaders.Count());
}

Wenn also eine Warnung für zu viele Abteilungsleiter ausgegeben wird, durchläuft .NET VIER MAL die Personen, vergleicht sie mit den Abteilungsleitern, sortiert sie nach Namen und zählt dann die Ergebnisobjekte.

Dies ist nur dann der Fall, wenn Personen und Abteilungen voreingestellte Wertsammlungen sind und keine Abfragen selbst.

Erik Hart
quelle
Ich könnte hinzufügen, dass .Count() > 0das das gleiche ist wie .Any().
jedmao
1
@sfjedi: Ich denke, es ist nicht dasselbe. Any () stoppt, wenn ein Element gefunden wurde, während Count () alle durchläuft. Wenn Sie also eine IEnumerable haben, die für eine verzögerte Ausführung möglich ist, sollte Any () für eine leere Prüfung bevorzugt werden.
Erik Hart
4
Wäre nicht .Any()effizienter als .Count() > 0damals? Übrigens beschwert sich Resharper immer darüber .Count() > 0. Deshalb spreche ich es mit Zuversicht an.
jedmao