2014-12-11 10 views
7

Stavo guardando il codice sorgente di Batch metodo e ho visto questo:Qual è lo scopo dell'uso di Select (x => x) in un metodo Batch?

// Select is necessary so bucket contents are streamed too 
yield return resultSelector(bucket.Select(x => x)); 

V'è un commento che non ho ben capito. Ho provato questo metodo senza usare Select e ha funzionato bene. Ma sembra che ci sia qualcosa che mi manca. Non riesco a pensare ad alcun esempio in cui ciò sarebbe necessario, quindi qual è lo scopo reale dell'uso di Select(x => x) qui?

Ecco il codice sorgente completo per riferimento:

private static IEnumerable<TResult> BatchImpl<TSource, TResult>(
     this IEnumerable<TSource> source, 
     int size, 
     Func<IEnumerable<TSource>, TResult> resultSelector) 
    { 
     TSource[] bucket = null; 
     var count = 0; 

     foreach (var item in source) 
     { 
      if (bucket == null) 
       bucket = new TSource[size]; 

      bucket[count++] = item; 

      // The bucket is fully buffered before it's yielded 
      if (count != size) 
       continue; 

      // Select is necessary so bucket contents are streamed too 
      yield return resultSelector(bucket.Select(x => x)); 

      bucket = null; 
      count = 0; 
     } 

     // Return the last bucket with all remaining elements 
     if (bucket != null && count > 0) 
      yield return resultSelector(bucket.Take(count)); 
    } 
+0

Esecuzione ritardata. L'iteratore non è accessibile finché non viene enumerato. –

+7

Questo era * probabilmente * il mio codice per iniziare. Hmm.Solitamente lo farei per evitare che un selettore di risultati trasmetta il codice sorgente a un array e poi lo scherzi, ma in questo caso non sarebbe dannoso. –

+1

@BradChristie Sarà ritardato anche senza 'Select' qui (qualunque cosa intendi). – BartoszKP

risposta

5

Per riassumere ciò che è nei commenti, in teoria questo è ridondante. In questo caso l'esecuzione differita è irrilevante. Al punto di yield completa esecuzione è già stata effettuata: il contenuto di bucket è già calcolato e non c'è nulla da differire.

C'è anche un problema causato dal comportamento di blocco iteratore - ogni volta che siamo di nuovo in questa implementazione il bucket è essere azzerato e ricostruito (bucket = null subito dopo yield). Anche se qualcuno dovesse trasmettere il risultato al tipo di array e modificarlo, non ci interessa.

Un vantaggio di questo approccio sembra essere solo eleganza: c'è una coerenza di tipo tra tutte le chiamate a . Senza il "ridondante" Select, il tipo effettivo sarebbe stato TSource[] la maggior parte del tempo e IEnumerable<TSource> per gli elementi finali che non hanno riempito l'intero bucket.

Tuttavia, si può immaginare il seguente scenario:

  1. qualcuno utilizza questa funzione si accorge che il tipo effettivo è un array
  2. a causa di qualche stimolo bisogno di migliorare le prestazioni, hanno gettato il lotto ricevuto a TSource[] (ad esempio, essi possono saltare elementi più efficiente, as Skip is not optimized for arrays)
  3. usano il metodo senza problemi, perché succede che Count() % size == 0 nel loro caso

Fino a quando, in seguito, si apre un elemento aggiuntivo che causa l'esecuzione dell'ultimo yield. E ora il cast a TSource[] avrà esito negativo.

Quindi, a seconda del numero di elementi e size, il metodo si comporterebbe in modo incoerente rispetto al tipo di risultato (passato al callback specificato). Si possono immaginare altri elaborati scenari in cui questa incoerenza può causare problemi, come alcuni ORM che, a seconda del tipo effettivo, serializzano gli oggetti in tabelle diverse. In questo contesto pezzi di dati finirebbero in diverse tabelle.

Questi scenari sono ovviamente tutti basati su altri errori e non provano che senza l'Select l'implementazione è errata. È comunque più friendly con il Select, in un certo senso, che riduce al minimo il numero di tali sfortunati scenari.

Problemi correlati