2010-09-23 11 views
40

Wiem, że to prawdopodobnie nie ma znaczenia/wpływa na wydajność w przeważającej części, ale nienawidzę idei uzyskania IEnumerable i robi .Count(). Czy istnieje IsEmpty lub NotEmpty lub jakaś funkcja? (Podobny do STL empty())IEnumerable jest pusty?

+0

Liczba może być bardzo nieskuteczna przy sprawdzaniu pustki, gdy podstawowa kolekcja nie jest ICollection , ponieważ zlicza wszystkie elementy przed powrotem, gdy wszystko, co chcesz wiedzieć, to co najmniej 1 element. –

+1

możliwy duplikat [C#: Zalecany sposób sprawdzenia, czy sekwencja jest pusta] (http://stackoverflow.com/questions/2094729/c-recommended-way-to-check-if-a-sequence-is-empty) – nawfal

Odpowiedz

71

Chcesz metodę rozszerzenia IEnumerable.Any() (.Net Framework 3.5 i wyżej). Pozwala uniknąć liczenia elementów.

+6

Do opracowania liczy się najwyżej 1 element i jest bardzo wydajny. –

+3

"... i jest bardzo wydajny" - jego efektywność zależy od implementacji. Jeśli jest to leniwy ewaluacja (np. Metoda wykorzystująca wydajność), to nawet rozpoczęcie wyliczenia może być kosztowne. – Joe

+9

Kosztowne nie oznacza, że ​​nie jest wydajne. –

8

bez potrzeby LINQ, można wykonać następujące czynności:

bool IsEmpty(IEnumerable en) 
{ 
    foreach(var c in en) { return false; } 
    return true; 
} 
+2

Jeszcze szybszy byłby powrót en.GetEnumerator(). MoveNext(), czyli w zasadzie to, co robi implementacja Any(). Zasadniczo robisz to samo z foreach, ale ponosisz dodatkowe koszty związane z przypisaniem pierwszej wartości do lokalnego var, którego nigdy nie będziesz używał. – KeithS

+0

@KeithS: to niezbyt bezpieczne: IEnumerable również może być IDisposable (dla kilku enumerables jest to bardzo istotne, na przykład, gdy masz próbę w pobliżu swoich deklaracji plonów). foreach dba o to, abyś prawidłowo się pozbywał. BTW, powyższa metoda jest prawie tym, co robi 'Any()'. – Ruben

+0

ok, za pomocą (iter = en.GetIterator()) return iter.MoveNext(); Nadal nie przypisujesz aktualnej wartości, tak jak w przypadku foreach. – KeithS

1

Na IEnumerable lub IEnumerable<T>, no.

Ale to naprawdę nie ma większego sensu. Jeśli kolekcja jest pusta i spróbujesz iterować ją przy użyciu numeru IEnumerable, wywołanie funkcji IEnumerator.MoveNext() zwróci wartość false bez żadnych kosztów wydajności.

+0

"... bez kosztów związanych z wydajnością" - jeśli nie jest to kolekcja, a zamiast niej leniwie wyliczone wyliczenie, może to wiązać się ze znacznymi kosztami. – Joe

+0

@Joe - Ale będziesz ponosić ten sam koszt wywoływania funkcji IEnumerable.Any() lub IEnumerable.Count(). –

+0

Zgadzam się. Dlatego w niektórych przypadkach warto rozważyć utworzenie listy, tak aby koszt ten był ponoszony tylko raz. Zobacz moją odpowiedź. – Joe

0

Nie sądzę, że to jest właśnie dla tego Count. Poza tym, co będzie szybciej:

  1. Dostęp do nieruchomości i pobierania zapisanego Integer
  2. Dostęp do nieruchomości i pobierania zapisanego Boolean
+2

Licznik IEnumerable może być dość drogi (na przykład wyliczenia z leniwą oceną). Moje rozwiązanie jest, jak sądzę, lepsze. – nothrow

+1

Na liście lub podobnym obiekcie, w którym liczba jest łatwo dostępna, będzie tańsza. Ale w ogólnej implementacji IEnumerable, naiwna implementacja dla Count() byłaby iteracją nad elementami, co jest potencjalnie dużym obciążeniem dla tego, o co prosi OP. – cristobalito

+0

@Yossarian: Nie wiedziałem, że istnieje funkcja 'Count()', każda znaleziona kolekcja używa właściwości, która zwraca zmienną prywatną. – Bobby

2

Można użyć metody rozszerzenie takich jak Obojętnie() lub hrabia(). Count() jest droższy niż Any(), ponieważ musi wykonać całe wyliczenie, jak zauważyli inni.

Ale w przypadku leniwej oceny (np. Metody wykorzystującej wydajność), każda z nich może być kosztowna. Na przykład, z następującym IEnumerable realizacji, każde wezwanie do Obojętnie czy hrabia będzie ponosić koszty nowej obie strony do bazy:

IEnumerable<MyObject> GetMyObjects(...) 
{ 
    using(IDbConnection connection = ...) 
    { 
     using(IDataReader reader = ...) 
     { 
      while(reader.Read()) 
      { 
       yield return GetMyObjectFromReader(reader); 
      } 
     } 
    } 
} 

myślę morał brzmi:

  • Jeśli tylko mieć IEnumerable<T> i chcesz zrobić coś więcej niż tylko wyliczyć (np. użyj Count lub Any), a następnie rozważ najpierw konwersję do listy (metoda rozszerzenia ToList). W ten sposób gwarantujesz wyliczenie tylko raz.

  • Jeśli projektowania API, która zwraca kolekcję, należy rozważyć powrót ICollection<T> (lub nawet IList<T>) zamiast IEnumerable<T> jak wielu ludzi wydaje się polecić. W ten sposób wzmacniasz swoją umowę, aby zagwarantować, że nie będziesz leniwy (a zatem nie będzie wielokrotnej oceny).

Uwaga mówię należy rozważyć powrocie zbiór, nie zawsze zwracać kolekcję. Jak zwykle pojawiają się kompromisy, jak wynika z komentarzy poniżej.

  • @KeithS myśli, nigdy nie powinno się wydajnością na DataReader, a jednocześnie nigdy nie mów nigdy, powiedziałbym, że to generalnie dobra rada, że ​​warstwa dostępu do danych powinny zwrócić ICollection<T> aniżeli leniwy ocenie IEnumerable<T>, z powodów, które daje KeithS w swoim komentarzu.

  • @Bear Monkey zauważa, że ​​tworzenie instancji listy może być kosztowne w powyższym przykładzie, jeśli baza danych zwróci dużą liczbę rekordów. To też jest prawdą, aw niektórych (prawdopodobnie rzadkich) przypadkach może być właściwe ignorowanie porad @ KeithS i zwracanie leniwo wyliczonego wyliczenia, pod warunkiem, że konsument robi coś, co nie jest zbyt czasochłonne (na przykład generuje pewne wartości zagregowane).

+0

+ 1 punkt dobry. –

+3

To jest zły przykład. NIGDY nie powinieneś ulegać na DataReaderze, z przyczyn innych niż wydajność (utrzymuje blokady w miejscu dłużej, przekształca strumień danych "firehose" w strunę itp.). Jednak wyrzucenie wszystkich danych do listy, a następnie ich przetworzenie, zachowałoby się podobnie i nadal ilustruje ten punkt. – KeithS

+1

ToListing przed powrotem może być nawet bardziej kosztowne niż powtarzające się koszty utworzenia, jeśli na przykład baza danych zwróci miliony rekordów. Id raczej zwrócić IEnumerable i niech konsument decyduje, czy chcą buforować wyniki z ToList. Również to, co powiedział Keith. –

2

Należy pamiętać, że IEnumerable to tylko interfejs. Implementacja za nim może być bardzo różna w różnych klasach (rozważmy przykład Joego). Metoda rozszerzenia IEnumerable.Any() musi być podejściem generycznym i może nie być tym, czego potrzebujesz (zgodnie z wydajnością). Yossarian sugeruje środki, które powinny działać dla wielu klas, ale jeśli podstawowa implementacja nie wykorzystuje "wydajności", możesz zapłacić cenę.

Ogólnie rzecz biorąc, jeśli trzymasz się kolekcji lub tablic zawiniętych w interfejs IEnumerable, to Cristobalito i Yossarian prawdopodobnie mają najlepsze odpowiedzi. Domyślam się, że wbudowana metoda .Any() ext robi to, co zaleca Yossarian.

0

Można również pisać własne przeciążeń metodę rozszerzenia hrabia tak:

/// <summary> 
    /// Count is at least the minimum specified. 
    /// </summary> 
    /// <typeparam name="TSource"></typeparam> 
    /// <param name="source"></param> 
    /// <param name="min"></param> 
    /// <returns></returns> 
    public static bool Count<TSource>(this IEnumerable<TSource> source, int min) 
    { 
     if (source == null) 
     { 
      throw new ArgumentNullException("source"); 
     } 
     return source.Count(min, int.MaxValue); 
    } 

    /// <summary> 
    /// Count is between the given min and max values 
    /// </summary> 
    /// <typeparam name="TSource"></typeparam> 
    /// <param name="source"></param> 
    /// <param name="min"></param> 
    /// <param name="max"></param> 
    /// <returns></returns> 
    public static bool Count<TSource>(this IEnumerable<TSource> source, int min, int max) 
    { 
     if (source == null) 
     { 
      throw new ArgumentNullException("source"); 
     } 
     if (min <= 0) 
     { 
      throw new ArgumentOutOfRangeException("min", "min must be a non-zero positive number"); 
     } 
     if (max <= 0) 
     { 
      throw new ArgumentOutOfRangeException("max", "max must be a non-zero positive number"); 
     } 
     if (min >= max) 
      throw new ArgumentOutOfRangeException("min and max", "min must be lest than max"); 

     var isCollection = source as ICollection<TSource>; 

     if (isCollection != null) 
      return isCollection.Count >= min && isCollection.Count <= max; 

     var count = 0; 
     using (var enumerator = source.GetEnumerator()) 
     { 
      while (enumerator.MoveNext()) 
      { 
       count++; 
       if (count >= min && count <= max) 
        return true; 
      } 
     } 
     return false; 
    } 
13

jeśli to nie jest nazwą rodzajową niż czymś jak ten

enumeration.Cast<object>().Any(); 

jeśli jest nazwą rodzajową, użycie rozbudowa przeliczalny jak było powiedział już:

0

Użyj Enumerable.Empty(), która zamiast IEnumerable.Any() uniemożliwi zakończenie listy o wartości NULL.

+0

Myślę, że użytkownik chce łatwy sposób sprawdzić, czy IEnumerable jest pusty, czy nie. Enumerable.Empty() zwraca wartość IEnumerable. Ale IEnumerable.Any() zwraca wartość logiczną. Dlatego myślę, że istotne dla tego pytania IEnumerable. Any() jest lepszy. https://msdn.microsoft.com/en-us/library/bb341042(v=vs.110).aspx https://msdn.microsoft.com/en-us/library/bb337697(v=vs. 110) .aspx –