Der Parameter ref oder out kann nicht in Lambda-Ausdrücken verwendet werden

172

Warum können Sie in einem Lambda-Ausdruck keinen ref- oder out-Parameter verwenden?

Ich bin heute auf den Fehler gestoßen und habe eine Problemumgehung gefunden, war aber immer noch neugierig, warum dies ein Fehler beim Kompilieren ist.

CS1628 : Der Parameter 'parameter' in ref oder out kann nicht in einer anonymen Methode, einem Lambda-Ausdruck oder einem Abfrageausdruck verwendet werden

Hier ist ein einfaches Beispiel:

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();
}
skalb
quelle
Es geht um Iteratoren, aber viele der gleichen Überlegungen in diesem Beitrag (auch von Eric Lippert & mdash; er ist schließlich im Sprachdesign-Team) gelten für Lambdas: < blogs.msdn.com/ericlippert/archive/2009/07/13 /… >
Joel Coehoorn
17
Darf ich fragen, welche Problemumgehung Sie gefunden haben?
Beatles1692
3
Sie können einfach eine lokale normale Variable deklarieren und damit arbeiten und das Ergebnis anschließend dem Wert zuweisen ... Fügen Sie einen var tempValue = value hinzu; und dann mit tempValue arbeiten.
Betrunkener Code-Affe

Antworten:

121

Lambdas scheinen die Lebensdauer der von ihnen erfassten Variablen zu ändern. Beispielsweise bewirkt der folgende Lambda-Ausdruck, dass der Parameter p1 länger als der aktuelle Methodenrahmen lebt , da auf seinen Wert zugegriffen werden kann, nachdem sich der Methodenrahmen nicht mehr auf dem Stapel befindet

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

Eine weitere Eigenschaft erfasster Variablen ist, dass Änderungen an der Variablen auch außerhalb des Lambda-Ausdrucks sichtbar sind. Zum Beispiel die folgenden Drucke 42

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

Diese beiden Eigenschaften erzeugen eine Reihe von Effekten, die auf folgende Weise einem ref-Parameter entgegenwirken

  • ref-Parameter können eine feste Lebensdauer haben. Überlegen Sie, ob Sie eine lokale Variable als ref-Parameter an eine Funktion übergeben möchten.
  • Nebenwirkungen im Lambda müssten auf dem ref-Parameter selbst sichtbar sein. Sowohl innerhalb der Methode als auch im Aufrufer.

Dies sind etwas inkompatible Eigenschaften und einer der Gründe, warum sie in Lambda-Ausdrücken nicht zulässig sind.

JaredPar
quelle
35
Ich verstehe, dass wir den refLambda-Ausdruck nicht verwenden können , aber der Wunsch, ihn zu verwenden, wurde nicht gestillt.
Zionpi
84

Unter der Haube wird die anonyme Methode implementiert, indem erfasste Variablen (worum es in Ihrem Fragetext geht) angehoben und als Felder einer vom Compiler generierten Klasse gespeichert werden. Es gibt keine Möglichkeit, einen refoder outParameter als Feld zu speichern . Eric Lippert hat es in einem Blogeintrag besprochen . Beachten Sie, dass es einen Unterschied zwischen erfassten Variablen und Lambda-Parametern gibt. Sie können "formale Parameter" wie die folgenden haben, da diese keine erfassten Variablen sind:

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);
}
Mehrdad Afshari
quelle
69

Sie können aber Sie müssen explizit alle Typen so definieren

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

Ist jedoch ungültig

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

Ist gültig

Ben Adams
quelle
13
Es tut; Frage ist, warum kannst du nicht; Antwort ist, dass Sie können.
Ben Adams
24
Es tut nicht; Die Frage ist, warum Sie nicht auf eine vorhandene Variable verweisen können , die bereits definiert ist refoder outsich in einem Lambda befindet. Es ist klar, ob Sie den Beispielcode lesen (versuchen Sie es erneut, um ihn erneut zu lesen). Die akzeptierte Antwort erklärt klar warum. Bei Ihrer Antwort geht es um die Verwendung refoder out Parameter des Lambda. Völlig nicht die Frage beantworten und über etwas anderes sprechen
edc65
4
@ edc65 ist richtig ... das hat nichts mit dem Thema der Frage zu tun, bei dem es um den Inhalt des Lamba-Ausdrucks (rechts) geht, nicht um seine Parameterliste (links). Es ist bizarr, dass dies 26 positive Stimmen hat.
Jim Balter
6
Es hat mir aber geholfen. +1 dafür. Danke
Emad
1
Aber ich verstehe immer noch nicht, warum es so konzipiert wurde. Warum muss ich alle Typen explizit definieren? Semantisch muss ich nicht. Verliere ich etwas
Joe
5

Da dies eines der Top-Ergebnisse für "C # lambda ref" bei Google ist; Ich denke, ich muss die obigen Antworten erweitern. Die ältere (C # 2.0) anonyme Delegatensyntax funktioniert und unterstützt komplexere Signaturen (sowie Schließungen). Lambdas und anonyme Delegierte haben zumindest die wahrgenommene Implementierung im Compiler-Backend geteilt (wenn sie nicht identisch sind) - und vor allem unterstützen sie Schließungen.

Was ich bei der Suche versucht habe, um die Syntax zu demonstrieren:

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

Denken Sie daran, dass Lambdas prozedural und mathematisch sicherer sind (aufgrund der bereits erwähnten Referenzwertförderung): Sie könnten eine Dose Würmer öffnen. Überlegen Sie genau, wenn Sie diese Syntax verwenden.

Jonathan Dickinson
quelle
3
Ich denke, Sie haben die Frage falsch verstanden. Die Frage war, warum ein Lambda in seiner Containermethode nicht auf ref / out-Variablen zugreifen konnte und nicht, warum das Lambda selbst keine ref / out-Variablen enthalten kann. AFAIK gibt es keinen guten Grund für Letzteres. Heute habe ich ein Lambda geschrieben (a, b, c, ref d) => {...}und refwurde mit der Fehlermeldung "Parameter '4' muss mit dem Schlüsselwort 'ref' deklariert werden" rot unterstrichen. Gesichtspalme! PS Was ist "Ref Value Promotion"?
Qwertie
1
@Qwertie Ich habe dies dazu gebracht, mit vollständiger Parametrisierung zu arbeiten, dh die Typen a, b, c und d einzuschließen, und es funktioniert. Siehe BenAdams Antwort (obwohl er auch die ursprüngliche Frage falsch versteht).
Ed Bayiates
@Qwertie Ich denke, ich habe nur die Hälfte dieses Punktes entfernt - ich denke, der ursprüngliche Punkt war, dass das Platzieren von ref-Parametern in einem Abschluss riskant sein könnte, aber ich muss später erkannt haben, dass dies in dem Beispiel, das ich gegeben habe (und auch nicht getan habe), nicht passiert ist Ich weiß, ob sich das überhaupt kompilieren würde.
Jonathan Dickinson
Dies hat nichts mit der tatsächlich gestellten Frage zu tun ... siehe die akzeptierte Antwort und die Kommentare unter der Antwort von Ben Adams, der die Frage ebenfalls missverstanden hat.
Jim Balter
1

Und vielleicht das?

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