2015-11-20 12 views
6

ho sempre pensato che questi due metodi erano simili:differenti risultati tra dei rendimenti e LINQ Select

public static IEnumerable<Func<int>> GetFunctions() 
{ 
    for(int i = 1; i <= 10; i++) 
     yield return new Func<int>(() => i); 
} 

public static IEnumerable<Func<int>> GetFunctionsLinq() 
{ 
    return Enumerable.Range(1, 10).Select(i => new Func<int>(() => i)); 
} 

Tuttavia, si ottengano risultati diversi quando li converte in List<Func<int>>:

List<Func<int>> yieldList = GetFunctions().ToList(); 
List<Func<int>> linqList = GetFunctionsLinq().ToList(); 

foreach(var func in yieldList) 
    Console.WriteLine("[YIELD] {0}", func()); 

Console.WriteLine("=================="); 

foreach(var func in linqList) 
    Console.WriteLine("[LINQ] {0}", func()); 

L'uscita is:

[YIELD] 11 
[YIELD] 11 
[YIELD] 11 
[YIELD] 11 
[YIELD] 11 
[YIELD] 11 
[YIELD] 11 
[YIELD] 11 
[YIELD] 11 
[YIELD] 11 
================== 
[LINQ] 1 
[LINQ] 2 
[LINQ] 3 
[LINQ] 4 
[LINQ] 5 
[LINQ] 6 
[LINQ] 7 
[LINQ] 8 
[LINQ] 9 
[LINQ] 10 

Perché è questo?

risposta

6

Questo è un problema di chiusura. È necessario memorizzare la variabile all'interno del ciclo per risolvere questo problema.

for (int i = 1; i <= 10; i++) 
{ 
    var i1 = i; 
    yield return new Func<int>(() => i1); 
} 

Infatti new Func<int>(() => i); utilizza il valore esatto dei contatori interni ciclo e non è una copia. Quindi, una volta terminato il ciclo, ottieni sempre 11, perché era l'ultimo valore impostato per il contatore.

+0

Nota che questo è stato "corretto" a partire da C# 5.0. http://stackoverflow.com/questions/12112881/has-foreachs-use-of-variables-been-changed-in-c-sharp-5 – juharr

+0

@juharr È stato modificato in cicli 'foreach'. 'I cicli for' sono rimasti gli stessi. –

+0

@JakubLortz È decisamente venerdì perché la mia comprensione della lettura è caduta sul pavimento. – juharr

1

Il i in for(int i = 1; i <= 10; i++) è la stessa variabile in ogni ciclo, solo cambiando valore.

() => i è una chiusura. Quando viene chiamato, utilizza il valore corrente di i, non il valore che aveva i quando veniva creato lo Func.

È prima necessario enumerare GetFunctions prima di chiamare le funzioni restituite, quindi i è già 11 in ciascuna di esse.

Se si chiama la funzione subito dopo inducendole dal enumeratore, si ottengono gli stessi risultati con la versione LINQ:

foreach (var f in GetFunctions()) 
    Console.WriteLine("[YIELD2] {0}", f()); 

In ogni caso, non è una buona idea per creare chiusure oltre variabili del loop.

Problemi correlati