2009-09-02 11 views
125

Perché non è possibile utilizzare un parametro ref o out in un'espressione lambda?Impossibile utilizzare il parametro ref o out nelle espressioni lambda

Mi sono imbattuto nell'errore di oggi e ho trovato una soluzione alternativa ma ero curioso di sapere perché si tratta di un errore in fase di compilazione.

Ecco un semplice esempio:

private void Foo() 
{ 
    int value; 
    Bar(out value); 
} 

private void Bar(out int value) 
{ 
    value = 3; 
    int[] array = { 1, 2, 3, 4, 5 }; 
    int newValue = array.Where(a => a == value).First(); 
} 
+10

posso chiedere quale fosse la soluzione che hai avuto trovato ? – Beatles1692

+0

Riguarda gli iteratori, ma gran parte dello stesso ragionamento in questo post (anche di Eric Lippert — si trova in fondo nel team di progettazione della lingua) si applica a lambdas:

risposta

94

lambda hanno l'aspetto di cambiare il corso della vita di variabili che catturano. Per esempio la seguente espressione lambda provoca il parametro p1 a vivo più lungo del frame attuale metodo come suo valore può essere letta dopo il fotogramma metodo non in pila

Func<int> Example(int p1) { 
    return() => p1; 
} 

Un'altra proprietà delle variabili acquisite è che è le modifiche alla variabile sono anche visibili al di fuori dell'espressione lambda. Ad esempio le seguenti stampe 42

void Example2(int p1) { 
    Action del =() => { p1 = 42; } 
    del(); 
    Console.WriteLine(p1); 
} 

Queste due proprietà producono un certo insieme di effetti che volare a fronte di un parametro ref nei seguenti modi

  • parametri ref possono avere una vita fissa. Prendi in considerazione il passaggio di una variabile locale come parametro di riferimento a una funzione.
  • Gli effetti collaterali nel lambda dovrebbero essere visibili sul parametro ref stesso. Sia all'interno del metodo che nel chiamante.

Queste sono proprietà un po 'incompatibili e sono una delle ragioni per cui non sono consentite nelle espressioni lambda.

+6

Capisco che non possiamo usare 'ref' dentro espressione lambda, ma il desiderio di usarlo non è stato alimentato . – zionpi

66

Sotto il cofano, il metodo anonimo è attuato da sollevamento variabili catturati (che è ciò che il vostro corpo domanda è tutto) e la loro memorizzazione come campi di un compilatore classe generata. Non è possibile memorizzare un parametro ref o out come campo. Eric Lippert ne ha parlato nel a blog entry. Si noti che esiste una differenza tra variabili acquisite e parametri lambda. È possibile avere "parametri formali" come il seguente in quanto non vengono acquisite variabili:

delegate void TestDelegate (out int x); 
static void Main(string[] args) 
{ 
    TestDelegate testDel = (out int x) => { x = 10; }; 
    int p; 
    testDel(out p); 
    Console.WriteLine(p); 
} 
5

Poiché questo è uno dei migliori risultati per "C# lambda ref" su Google; Sento che ho bisogno di espandere le risposte di cui sopra. La sintassi del delegato anonimo più vecchio (C# 2.0) funziona e supporta firme più complesse (come pure le chiusure). I delegati Lambda e anonimi hanno perlomeno condiviso un'implementazione percepita nel backend del compilatore (se non sono identici) e, cosa più importante, supportano le chiusure.

Quello che stavo cercando di fare quando ho fatto la ricerca, per dimostrare la sintassi:

public static ScanOperation<TToken> CreateScanOperation(
    PrattTokenDefinition<TNode, TToken, TParser, TSelf> tokenDefinition) 
{ 
    var oldScanOperation = tokenDefinition.ScanOperation; // Closures still work. 
    return delegate(string text, ref int position, ref PositionInformation currentPosition) 
     { 
      var token = oldScanOperation(text, ref position, ref currentPosition); 
      if (token == null) 
       return null; 
      if (tokenDefinition.LeftDenotation != null) 
       token._led = tokenDefinition.LeftDenotation(token); 
      if (tokenDefinition.NullDenotation != null) 
       token._nud = tokenDefinition.NullDenotation(token); 
      token.Identifier = tokenDefinition.Identifier; 
      token.LeftBindingPower = tokenDefinition.LeftBindingPower; 
      token.OnInitialize(); 
      return token; 
     }; 
} 

Basta tenere a mente che Lambda sono proceduralmente e matematicamente sicuro (a causa della promozione del valore ref accennato in precedenza): potresti aprire una lattina di vermi. Pensa attentamente quando usi questa sintassi.

+3

Penso che tu abbia frainteso la domanda. La domanda era: perché un lambda non poteva accedere alle variabili ref/out nel suo metodo contenitore, non perché lo _lambda stesso_ non potesse contenere variabili di ref/out. AFAIK non c'è una buona ragione per quest'ultimo. Oggi ho scritto un lambda '(a, b, c, ref d) => {...}' e 'ref' è stato sottolineato in rosso con il messaggio di errore" Il parametro '4' deve essere dichiarato con la parola chiave 'ref' ". Facepalm! Post scriptum che cos'è la "promozione del valore ref"? – Qwertie

+1

@Qwertie Ho capito che funziona con parametrizzazione completa, ovvero include i tipi su a, b, c e d e funziona. Vedi la risposta di BenAdams (anche se fraintende anche la domanda originale). –

+0

@Qwertie Penso di aver rimosso solo metà di quel punto - penso che il punto originale fosse che mettere i parametri di riferimento in una chiusura poteva essere rischioso, ma devo aver realizzato successivamente che ciò non stava accadendo nell'esempio che ho dato (e so se lo farebbe anche compilando). –

39

È possibile, ma è necessario definire in modo esplicito tutti i tipi così

(a, b, c, ref d) => {...} 

non è valida, tuttavia

(int a, int b, int c, ref int d) => {...} 

è valida

+3

Sebbene interessante, questo non risponde alla domanda. Prova a leggerlo di nuovo – edc65

+8

Lo fa; la domanda è perché non puoi; la risposta è che puoi. –

+11

Non funziona; la domanda è perché non puoi fare riferimento a una * variabile esistente *, già definita 'ref' o' out', all'interno di una lambda. È chiaro se leggi il codice di esempio (riprova a leggerlo di nuovo). La risposta accettata spiega chiaramente perché. La tua risposta riguarda l'uso di 'ref' o' out' * parameter * per lambda. Totalmente non rispondere alla domanda e parlare di qualcos'altro – edc65

Problemi correlati