2012-03-09 22 views
17

Perché non è possibile utilizzare sia il rendimento sia il rendimento restituito nello stesso metodo?Perché non è possibile utilizzare "return" e "yield return" nello stesso metodo?

Ad esempio, possiamo avere GetIntegers1 e GetIntegers2 di seguito, ma non GetIntegers3.

public IEnumerable<int> GetIntegers1() 
{ 
    return new[] { 4, 5, 6 }; 
} 

public IEnumerable<int> GetIntegers2() 
{ 
    yield return 1; 
    yield return 2; 
    yield return 3; 
} 

public IEnumerable<int> GetIntegers3() 
{ 
    if (someCondition) 
    { 
    return new[] {4, 5, 6}; // compiler error 
    } 
    else 
    { 
    yield return 1; 
    yield return 2; 
    yield return 3; 
    } 
} 
+11

aspetta un secondo, jon skeet verrà ora. – Juvanis

+0

Aggiungerò che se davvero ne hai bisogno, potresti creare un GetIngegers4 che chiama GetIntegers1 o GetIntegers2 a seconda di una condizione. – xanatos

+0

Questo è probabilmente ovvio, ma in questi casi è sempre possibile srotolare la raccolta e restituire gli articoli: foreach (var item in new [] {4,5,6}) yield return item; – Foo42

risposta

16

return è impaziente. Restituisce l'intero set di risultati in una sola volta. yield return crea un enumeratore. Dietro le quinte il compilatore C# emette la classe necessaria per l'enumeratore quando si utilizza yield return. Il compilatore non cerca le condizioni di runtime come if (someCondition) quando determina se deve emettere il codice per un enumerable o ha un metodo che restituisce un array semplice. Rileva che nel tuo metodo stai usando entrambi i quali non è possibile in quanto non può emettere il codice per un enumeratore e allo stesso tempo il metodo restituisce un array normale e tutto questo per lo stesso metodo.

+0

Quando provo a eseguire il debug del codice, posso vedere che percorre tutte le linee di codice nell'iterazione. Allora come possiamo dire che costruisce l'enumeratore internamente e lo restituisce solo una volta ... – Karan

7

Il compilatore riscrive qualsiasi metodo con un'istruzione yield (ritorno o interruzione). Attualmente non può gestire metodi che possono o meno yield.

Suggerirei di leggere il capitolo 6 di Jon Skeet C# in Depth, di cui il capitolo 6 è disponibile gratuitamente - copre abbastanza facilmente i blocchi iteratori.

Non vedo alcun motivo per cui ciò non sarebbe possibile nelle versioni future del compilatore C#. Altre lingue .Net supportano qualcosa di simile nella forma di un operatore 'yield from' (See F# yield!). Se un tale operatore esisteva in C# permetterebbe di scrivere il codice in forma:

public IEnumerable<int> GetIntegers() 
{ 
    if (someCondition) 
    { 
    yield! return new[] {4, 5, 6}; 
    } 
    else 
    { 
    yield return 1; 
    yield return 2; 
    yield return 3; 
    } 
} 
10

No, non è possibile farlo - un blocco iteratore (qualcosa con un yield) non può usare il regolare (non-yield) return. Invece, è necessario utilizzare 2 metodi:

public IEnumerable<int> GetIntegers3() 
{ 
    if (someCondition) 
    { 
    return new[] {4, 5, 6}; // compiler error 
    } 
    else 
    { 
    return GetIntegers3Deferred(); 
    } 
} 
private IEnumerable<int> GetIntegers3Deferred() 
{ 
    yield return 1; 
    yield return 2; 
    yield return 3; 
} 

o dal in questo caso specifico il codice sia già esiste negli altri 2 metodi:

public IEnumerable<int> GetIntegers3() 
{ 
    return (someCondition) ? GetIntegers1() : GetIntegers2(); 
} 
3

Teoricamente ho non credo sia alcuna ragione perché di ritorno e dei rendimenti non possono essere mescolati: sarebbe una cosa facile per il compilatore di prima sintatticamente trasformare qualsiasi return (blabla()); frase in:

var myEnumerable = blabla(); 
foreach (var m in myEnumerable) 
    yield return m; 
yield break; 

e poi continua (per trasformare l'intero metodo in ... qualunque cosa lo trasformino ora; un interno di classe IEnumerator anonima ?!)

Allora perché non scelgono per la sua attuazione, qui ci sono due ipotesi:

  • potrebbero hanno deciso che sarebbe stato fonte di confusione per gli utenti di avere e rendimento rendimento allo stesso tempo,

  • Il ritorno dell'intero enumerable è più veloce ed economico ma anche desideroso; costruire tramite yield return è un po 'più costoso (specialmente se chiamato in modo ricorsivo, vedere l'avvertimento di Eric Lippert per attraversare alberi binari con dichiarazioni di rendimento qui: https://stackoverflow.com/a/3970171/671084 per esempio) ma pigro. Quindi un utente di solito non vorrebbe mescolare questi: Se non hai bisogno di pigrizia (ad es.conosci l'intera sequenza) non subire la penalità di efficienza, basta usare un metodo normale. Potrebbero aver voluto costringere l'utente a pensare in questo modo.

D'altra parte, sembra che ci siano situazioni in cui l'utente potrebbe beneficiare di alcune estensioni sintattiche; potresti voler leggere questa domanda e le risposte come un esempio (non la stessa domanda ma probabilmente con un motivo simile): Yield Return Many?

+0

Questa è in realtà una risposta a "Quali potrebbero essere state le ragioni per cui Microsoft ha deciso di non supportare ...", che sarebbe stato un migliore domanda in primo luogo. –

0

Penso che il motivo principale per cui non funziona è perché lo progettiamo in un modo che non è complicato ma è performante allo stesso tempo sarebbe difficile, con relativamente poco beneficio.

Che cosa farà esattamente il codice? Restituirebbe direttamente la matrice o la itererà sopra?

Se restituisce direttamente l'array, è necessario elaborare regole complicate in base alle condizioni consentite da return, poiché return dopo il yield return non ha senso. E probabilmente dovresti generare un codice complicato per decidere se il metodo restituirà un iteratore personalizzato o l'array.

Se si desidera iterare la raccolta, probabilmente si desidera una parola chiave migliore. Something like yield foreach. Questo è stato effettivamente considerato, ma alla fine non è stato implementato. Penso di aver letto che la ragione principale è che è davvero difficile farlo funzionare bene, se hai diversi iteratori nidificati.

+1

È difficile farlo funzionare bene con iteratori nidificati, ma il problema può essere risolto con un po 'di intelligenza; lo hanno fatto in C-Omega. Tuttavia, se lo fai, inizi a incorrere in problemi di correttezza quando questi iteratori nidificati contengono la gestione delle eccezioni. È una bella idea e vorrei che potessimo farlo, ma il rapporto dolore-guadagno è troppo alto. –