2010-09-23 14 views
40

So che probabilmente non importa/influisce sulle prestazioni per la maggior parte, ma odio l'idea di ottenere un IEnumerable e di fare .Count(). C'è un IsEmpty o NotEmpty o qualche funzione? (Simile a Stl vuota())IEnumerable è vuoto?

+0

Il conteggio può essere molto inefficiente quando si verifica la presenza di vuoto quando la raccolta sottostante non è un ICollection poiché conteggia tutti gli elementi prima di tornare quando tutto ciò che si desidera sapere è almeno 1 elemento. –

+1

possibile duplicato di [C#: metodo consigliato per verificare se una sequenza è vuota] (http://stackoverflow.com/questions/2094729/c-recommended-way-to-check-if-a-sequence-is-empty) – nawfal

risposta

71

Si desidera il metodo di estensione IEnumerable.Any() (.Net Framework 3.5 e versioni successive). Evita il conteggio degli elementi.

+6

Per elaborarlo conta al massimo 1 elemento ed è molto efficiente. –

+3

"... ed è molto efficiente" - l'efficienza dipende dall'implementazione. Se si tratta di un'enumerazione con valutazione lenta (ad esempio un metodo che utilizza la resa), anche l'avvio dell'enumerazione può essere costoso. – Joe

+9

Costoso non significa che non sia efficiente. –

8

senza bisogno di LINQ, si può fare seguente:

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

Ancora più veloce sarebbe il ritorno en.GetEnumerator(). MoveNext(), che è fondamentalmente l'implementazione di Any(). Stai praticamente facendo lo stesso con il foreach, ma stai sostenendo costi aggiuntivi per l'assegnazione del primo valore a una var locale, che non userai mai. – KeithS

+0

@KeithS: non è molto sicuro: un IEnumerable può essere IDisposable (per alcuni enumerabili questo è molto rilevante, ad esempio quando si prova, infine, attorno alle dichiarazioni di rendimento). foreach si prende cura di smaltire correttamente. A proposito, il metodo sopra è praticamente ciò che fa Any(). – Ruben

+0

ok allora, usando (iter = en.GetIterator()) restituisce iter.MoveNext(); Non stai ancora assegnando il valore corrente come faresti con un foreach. – KeithS

1

On IEnumerable o IEnumerable<T>, no.

Ma in realtà non ha molto senso. Se una raccolta è vuota e si tenta di iterare su di essa utilizzando IEnumerable, la chiamata a IEnumerator.MoveNext() restituirà semplicemente false senza alcun costo di prestazioni.

+0

"... senza costi di performance" - se non è una raccolta, ma invece un'enumerazione con valutazione lenta, potrebbe esserci un costo significativo. – Joe

+0

@Joe - Ma dovrai sostenere lo stesso costo chiamando IEnumerable.Any() o IEnumerable.Count(). –

+0

Sono d'accordo. Quindi in alcuni casi vale la pena considerare l'istanziazione di un elenco in modo tale che tale costo venga sostenuto una sola volta. Vedi la mia risposta. – Joe

0

Io non la penso così, questo è ciò che è Count. Inoltre, ciò che sarà più veloce:

  1. accesso a una proprietà e il recupero di una stored Integer
  2. accesso a una proprietà e il recupero di una stored Boolean
+2

Il conteggio su IEnumerable può essere piuttosto costoso (ad esempio, enumerables con valutazione lazy). La mia soluzione è, penso, migliore. – nothrow

+1

In un elenco o oggetto simile in cui Count è facilmente disponibile, sarà più economico. Ma in un'implementazione IEnumerable generale, l'implementazione ingenua di Count() sarebbe quella di scorrere gli elementi, che è potenzialmente un sovraccarico per ciò che l'OP sta chiedendo. – cristobalito

+0

@Yossarian: non sapevo che esistesse una funzione 'Count()', ogni collezione che trovo utilizza una proprietà che restituisce una variabile privata. – Bobby

2

È possibile utilizzare metodi di estensione, come qualsiasi() o Count(). Count() è più costoso di Any(), poiché deve eseguire l'intera enumerazione, come altri hanno sottolineato.

Tuttavia, nel caso di una valutazione lenta (ad esempio un metodo che utilizza la resa), può essere costoso. Ad esempio, con il IEnumerable implementazione seguente, ogni chiamata a qualunque o Count dovranno sostenere il costo di un nuovo andata e ritorno al database:

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

Credo che la morale è:

  • Se solo avere un IEnumerable<T>, e si vuole fare qualcosa di più che enumerarlo (es. usare Count o Any), quindi considerare prima di convertirlo in un List (metodo di estensione ToList). In questo modo garantisci di enumerare solo una volta.

  • Se si progetta un'API che restituisce un insieme, in considerazione di ritornare ICollection<T> (o anche IList<T>) piuttosto che IEnumerable<T> come molte persone sembrano raccomandare. In tal modo si rafforza il contratto per garantire nessuna valutazione pigra (e quindi nessuna valutazione multipla).

Si prega di notare che sto dicendo si dovrebbe prendere in considerazione restituire una collezione, non sempre restituire un insieme. Come sempre ci sono trade-off, come si può vedere dai commenti qui sotto.

  • @KeithS pensa si dovrebbe mai cedere su un DataReader, e mentre non ho mai dire mai, direi che è generalmente un buon consiglio che un Data Access Layer dovrebbe restituire un ICollection<T> piuttosto che un pigro-valutato IEnumerable<T>, per le ragioni che KeithS dà nel suo commento.

  • @Bear Monkey osserva che l'istanziazione di un elenco può essere costosa nell'esempio precedente se il database restituisce un numero elevato di record. Anche questo è vero, e in alcuni casi (probabilmente rari) potrebbe essere opportuno ignorare il consiglio di @ KeithS e restituire un'enumerazione valutata pigramente, a condizione che il consumatore stia facendo qualcosa che non richiede troppo tempo (ad esempio generando alcuni valori aggregati).

+0

+ 1 punto buono. –

+3

Questo è un cattivo esempio. Non dovresti MAI cedere su un DataReader, per ragioni diverse dalle prestazioni (mantiene i lock più a lungo, trasforma un flusso di dati "firehose" in un rivolo, ecc.). Tuttavia, lo slurping di tutti i dati in una lista, quindi cedendo attraverso di essa, si comporterebbe allo stesso modo e illustrerebbe ancora il punto. – KeithS

+1

ToListing prima di tornare potrebbe essere ancora più costoso dei costi di istanza ripetuti se, ad esempio, il database restituisce milioni di record. L'ID restituisce invece IEnumerable e consente al consumatore di decidere se desidera memorizzare nella cache i risultati con ToList. Anche quello che ha detto Keith. –

2

Tenere presente che IEnumerable è solo un'interfaccia. L'implementazione alla base può essere molto diversa da una classe all'altra (si consideri l'esempio di Joe). Il metodo di estensione IEnumerable.Any() deve essere un approccio generico e potrebbe non essere quello che desideri (rendimento saggio). Yossarian suggerisce un mezzo che dovrebbe funzionare per molte classi, ma se l'implementazione sottostante non usa "yield" si potrebbe comunque pagare un prezzo.

In genere, se ci si attiene a raccolte o array racchiusi in un'interfaccia IEnumerable, Cristobalito e Yossarian probabilmente hanno le risposte migliori. La mia ipotesi è il built-in metodo .ny() ext fa quello che Yossarian raccomanda.

0

è anche possibile scrivere un proprio metodo di estensione Conte sovraccarichi in questo modo:

/// <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

se non è generica di smth come questo

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

se è generico, uso estensione di Enumerable come era detto già

0

Utilizzare Enumerable.Empty() che anziché IEnumerable.Any() impedirà di terminare con l'elenco Null.

+0

Penso che l'utente voglia un modo semplice per verificare se un oggetto IEnumerable è vuoto o meno. Enumerable.Empty() restituisce un oggetto IEnumerable. Ma IEnumerable.Any() restituisce il valore booleano. Perciò penso che sia rilevante per questa domanda IEnumerable.Any() è meglio. https://msdn.microsoft.com/en-us/library/bb341042(v=vs.110).aspx https://msdn.microsoft.com/en-us/library/bb337697(v=vs. 110) aspx –