2009-08-01 12 views
77

ho pensato che sarebbe bello fare qualcosa di simile (con il lambda facendo un ritorno resa):In C#, perché un metodo anonimo non può contenere una dichiarazione di rendimento?

public IList<T> Find<T>(Expression<Func<T, bool>> expression) where T : class, new() 
{ 
    IList<T> list = GetList<T>(); 
    var fun = expression.Compile(); 

    var items =() => { 
     foreach (var item in list) 
      if (fun.Invoke(item)) 
       yield return item; // This is not allowed by C# 
    } 

    return items.ToList(); 
} 

Tuttavia, ho scoperto che non posso utilizzare resa in modo anonimo. Mi sto chiedendo perché. Il yield docs dice solo che non è permesso.

Poiché non era consentito, ho appena creato Elenco e aggiunto gli elementi.

+0

Ora che possiamo avere anonimi 'lambda async' permettendo' await' interno in C# 5.0, sarei interessato a sapere il motivo per cui ancora rifugio implementare iteratori anonimi con 'yield' inside. Più o meno, è lo stesso generatore di macchine di stato. – Noseratio

risposta

91

Eric Lippert ha recentemente scritto una serie di post di blog sul perché la resa non è consentito in alcuni casi.

EDIT2:

0.123.516,41 mila
  • Part 7(questa è stata pubblicata in seguito e si rivolge specificamente a questa domanda)

Probabilmente troverete la risposta ci ...


Edit1: questo è spiegato nel commenti della Parte 5, nella risposta di Eric al commento di Abhijeet Patel:

Q:

Eric,

Puoi anche fornire qualche informazione in perché "rese" non sono ammessi all'interno di un metodo anonimo o lambda espressione

A:

Buona domanda . Mi piacerebbe avere blocchi di iteratore anonimi . Sarebbe del tutto eccezionale essere in grado di costruire un generatore di sequenze sul posto che si chiudeva sulle variabili locali . Il motivo per cui non lo è è il semplice: i vantaggi non sono superiori ai costi.La bellezza di che crea i generatori di sequenza sul posto è in realtà piuttosto piccola nel grande schema di cose e nei metodi nominali fare il lavoro abbastanza bene nella maggior parte degli scenari . Quindi i vantaggi non sono convincenti.

I costi sono elevati. La riscrittura di Iterator è la più complicata trasformazione di nel compilatore e la riscrittura del metodo anonimo di è la seconda più complicata di . I metodi anonimi possono essere all'interno di altri metodi anonimi e i metodi anonimi possono essere all'interno di blocchi iteratori. Pertanto, ciò che facciamo è prima di tutto riscrivere tutti i metodi anonimi in modo che diventino i metodi di una classe di chiusura. Questa è la penultima cosa che il compilatore fa prima di emettere IL per un metodo. Una volta completato questo passaggio, il reeritor iteratore può supporre che non vi siano metodi anonimi nel blocco iteratore ; sono già stati riscritti tutti . Pertanto il reporter autore iteratore può semplicemente concentrarsi su riscrivendo l'iteratore, senza preoccupandosi che ci possa essere un metodo anonimo non realizzato .

Inoltre, i blocchi iteratori non "nidificano" mai, a differenza dei metodi anonimi. Il reiteratore iteratore può supporre che tutti i blocchi iteratore siano "di primo livello".

Se i metodi anonimi sono consentiti per contengono blocchi iteratore, quindi entrambi gli assunti escono dalla finestra. Si può avere un blocco iteratore che contiene un metodo anonimo che contiene un metodo anonimo che contiene un blocco iteratore che contiene un metodo anonimo, e ... schifo. Ora dobbiamo scrivere una riscrittura pass in grado di gestire i blocchi iteratori nidificati e i metodi anonimi nidificati allo allo stesso tempo, unendo i nostri due più algoritmi in un algoritmo più complicato . Sarebbe essere davvero difficile da progettare, implementare, e test. Siamo abbastanza intelligenti da fare quindi, ne sono sicuro. Abbiamo una squadra intelligente qui. Ma non vogliamo assumere lo che un grosso onere per una funzionalità "bella da avere ma non necessaria". - Eric

+0

Interessante, soprattutto perché ora ci sono funzioni locali. – Mafii

2

Purtroppo non so perché non hanno permesso questo, dal momento che è del tutto possibile immaginare come funzionerebbe.

Tuttavia, i metodi anonimi sono già un pezzo di "magia del compilatore" nel senso che il metodo verrà estratto in un metodo nella classe esistente o anche in una classe completamente nuova, a seconda che si tratti di locale variabili o no.

Inoltre, i metodi di iterazione che utilizzano yield vengono implementati anche utilizzando il compilatore magico.

La mia ipotesi è che uno di questi due rende il codice non identificabile con l'altro pezzo di magia, e che è stato deciso di non perdere tempo a fare questo lavoro per le versioni correnti del compilatore C#. Certo, potrebbe non essere affatto una scelta concisa e semplicemente non funziona perché nessuno ha pensato di implementarlo.

Per una domanda precisa al 100%, suggerirei di utilizzare il sito Microsoft Connect e segnalare una domanda, sono sicuro che otterrete qualcosa di utile in cambio.

18

Eric Lippert ha scritto un eccellente serie di articoli sui limiti (e le decisioni di progettazione che influenzano tali scelte) su iterator blocks

In particolare blocchi iteratore sono attuati da alcune trasformazioni di codice compilatore sofisticati. Queste trasformazioni avrebbero un impatto con le trasformazioni che avvengono all'interno di funzioni anonime o lambda in modo tale che in determinate circostanze entrambi tenterebbero di "convertire" il codice in qualche altro costrutto che fosse incompatibile con l'altro.

Di conseguenza sono vietati dall'interazione.

Come i blocchi iteratori funzionano sotto il cofano viene trattato bene here.

Come semplice esempio di un'incompatibilità:

public IList<T> GreaterThan<T>(T t) 
{ 
    IList<T> list = GetList<T>(); 
    var items =() => { 
     foreach (var item in list) 
      if (fun.Invoke(item)) 
       yield return item; // This is not allowed by C# 
    } 

    return items.ToList(); 
} 

Il compilatore viene simultaneamente voler convertire questo in modo seguente:

// inner class 
private class Magic 
{ 
    private T t; 
    private IList<T> list; 
    private Magic(List<T> list, T t) { this.list = list; this.t = t;} 

    public IEnumerable<T> DoIt() 
    { 
     var items =() => { 
      foreach (var item in list) 
       if (fun.Invoke(item)) 
        yield return item; 
     } 
    } 
} 

public IList<T> GreaterThan<T>(T t) 
{ 
    var magic = new Magic(GetList<T>(), t) 
    var items = magic.DoIt(); 
    return items.ToList(); 
} 

e allo stesso tempo l'aspetto iteratore sta cercando di fare è lavoro per fare una piccola macchina statale. Alcuni semplici esempi potrebbero funzionare con una notevole quantità di controllo dell'integrità (prima affrontare i (chiusure eventualmente arbitrariamente nexted) poi vedere se il livello di fondo risultante classi potrebbe essere trasformato in macchine a stati iteratore.

Tuttavia questo sarebbe

  1. Un sacco di lavoro.
  2. Potrebbe non funzionare in tutti i casi senza che l'aspetto del blocco iteratore sia in grado di impedire che l'aspetto di chiusura applichi determinate trasformazioni per l'efficienza (come la promozione di variabili locali a variabili di istanza piuttosto che una classe di chiusura completa).
    • Se ci fosse anche una piccola possibilità di sovrapposizione in cui era impossibile o sufficientemente difficile non essere attuato allora il numero di problemi di supporto con conseguente sarebbe probabilmente elevata, in quanto la variazione di rottura sottile sarebbe perso su molti utenti.
  3. Può essere facilmente lavorato.

Nel tuo esempio in questo modo:

public IList<T> Find<T>(Expression<Func<T, bool>> expression) 
    where T : class, new() 
{ 
    return FindInner(expression).ToList(); 
} 

private IEnumerable<T> FindInner<T>(Expression<Func<T, bool>> expression) 
    where T : class, new() 
{ 
    IList<T> list = GetList<T>(); 
    var fun = expression.Compile(); 
    foreach (var item in list) 
     if (fun.Invoke(item)) 
      yield return item; 
} 
+1

Non c'è una chiara ragione per cui il compilatore non può, una volta eliminata tutte le chiusure, eseguire la normale trasformazione dell'iteratore. Sai di un caso che in realtà potrebbe presentare qualche difficoltà? A proposito, la tua classe 'Magic' dovrebbe essere' Magic '. – Qwertie

1

mi farebbe questo:

IList<T> list = GetList<T>(); 
var fun = expression.Compile(); 

return list.Where(item => fun.Invoke(item)).ToList(); 

naturalmente è necessario lo System.Core.dll riferimento da .NET 3.5 per il metodo LINQ. E includono:

using System.Linq; 

Cheers,

Sly

Problemi correlati