Impossibile utilizzare il parametro ref o out nelle espressioni lambda


173

Perché non puoi usare un parametro ref o out in un'espressione lambda?

Oggi ho riscontrato l'errore e ho trovato una soluzione alternativa, ma ero ancora curioso di sapere perché si tratta di un errore di compilazione.

CS1628 : Impossibile utilizzare nel parametro ref o out 'parametro' all'interno di un metodo anonimo, espressione lambda o espressione di query

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();
}

Riguarda gli iteratori, ma lo stesso ragionamento in questo post (anche di Eric Lippert e mdash; fa parte del team di progettazione linguistica dopo tutto) si applica a lambdas : < blogs.msdn.com/ericlippert/archive/2009/07/13 / ... >
Joel Coehoorn,

17
Posso chiederti qual è stata la soluzione alternativa che hai trovato?
Beatles1692,

3
Puoi semplicemente dichiarare una variabile normale locale e lavorare con quella, e assegnare il risultato al valore in seguito ... Aggiungi un var tempValue = value; e quindi lavora con tempValue.
Drunken Code Monkey,

Risposte:


122

Le lambda hanno l'aspetto di cambiare la durata delle variabili che catturano. Ad esempio la seguente espressione lambda fa sì che il parametro p1 viva più a lungo del frame del metodo corrente poiché è possibile accedere al suo valore dopo che il frame del metodo non è più nello stack

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

Un'altra proprietà delle variabili acquisite è che le modifiche alla variabile sono visibili anche all'esterno 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 volano di fronte a un parametro ref nei modi seguenti

  • i parametri ref potrebbero avere una durata fissa. Considera di passare una variabile locale come parametro ref a una funzione.
  • Gli effetti collaterali nella lambda dovrebbero essere visibili sul parametro ref stesso. Sia all'interno del metodo che nel chiamante.

Queste sono proprietà alquanto incompatibili e sono uno dei motivi per cui non sono consentite nelle espressioni lambda.


36
Capisco che non possiamo usare l' refespressione lambda, ma il desiderio di usarla non è stato alimentato.
zionpi,

85

Sotto il cofano, il metodo anonimo viene implementato sollevando le variabili catturate (che è ciò di cui tratta il corpo della domanda) e memorizzandole come campi di una classe generata dal compilatore. Non è possibile memorizzare un parametro refo outcome campo. Eric Lippert ne ha discusso in un post di blog . Si noti che esiste una differenza tra variabili acquisite e parametri lambda. È possibile avere "parametri formali" come il seguente come sono variabili non catturati:

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);
}

70

Puoi ma devi definire esplicitamente tutti i tipi in questo modo

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

Non è valido, tuttavia

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

È valido


13
Lo fa; la domanda è: perché non puoi? la risposta è che puoi.
Ben Adams,

24
Non lo fa; domanda è perché non puoi fare riferimento a una variabile esistente , già definita refo 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'utilizzo refo il out parametro della lambda. Totalmente non rispondere alla domanda e parlare di qualcos'altro
edc65

4
@ edc65 ha ragione ... questo non ha nulla a che fare con l'oggetto della domanda, che riguarda il contenuto dell'espressione lamba (a destra), non il suo elenco di parametri (a sinistra). È strano che questo abbia ottenuto 26 voti.
Jim Balter,

6
Mi ha aiutato però. +1 per quello. Grazie
Emad

1
Ma ancora non capisco perché è stato progettato per essere così. Perché devo definire esplicitamente tutti i tipi? Semanticamente non ne ho bisogno. Sto perdendo qualcosa?
Joe,

5

Poiché questo è uno dei migliori risultati di "ref # lambda C" su Google; Sento di aver bisogno di espandere le risposte di cui sopra. La sintassi del delegato anonimo (C # 2.0) precedente funziona e supporta firme più complesse (nonché chiusure). Almeno i delegati Lambda e anonimi hanno condiviso l'implementazione percepita nel backend del compilatore (se non sono identici) - e, soprattutto, 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;
        };
}

Ricorda solo che Lambdas è proceduralmente e matematicamente più sicuro (a causa della promozione del valore di riferimento menzionata in precedenza): potresti aprire una lattina di worm. Pensa attentamente quando usi questa sintassi.


3
Penso che tu abbia frainteso la domanda. La domanda era perché un lambda non potesse accedere alle variabili ref / out nel suo metodo container, non perché lo stesso lambda non potesse contenere variabili ref / out. AFAIK non c'è una buona ragione per quest'ultima. Oggi ho scritto un lambda (a, b, c, ref d) => {...}ed è refstato sottolineato in rosso con il messaggio di errore "Il parametro '4' deve essere dichiarato con la parola chiave 'ref'". Facepalm! PS cos'è la "promozione del valore di riferimento"?
Qwertie

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

@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 potesse essere rischioso, ma devo aver successivamente capito che ciò non stava accadendo nell'esempio che ho dato (e nemmeno So se questo potrebbe anche compilare).
Jonathan Dickinson,

Questo non ha nulla a che fare con la domanda effettivamente posta ... vedi la risposta accettata e i commenti sotto la risposta di Ben Adams, che allo stesso modo ha frainteso la domanda.
Jim Balter,

1

E forse questo?

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

private void Bar(out int value)
{
    value = 3;
    int[] array = { 1, 2, 3, 4, 5 };
    var val = value; 
    int newValue = array.Where(a => a == val).First();
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.