Nie można użyć parametru ref lub out w wyrażeniach lambda

173

Dlaczego nie możesz użyć parametru ref lub out w wyrażeniu lambda?

Natknąłem się dzisiaj na błąd i znalazłem obejście, ale nadal byłem ciekawy, dlaczego jest to błąd czasu kompilacji.

CS1628 : Nie można użyć w parametrze ref lub out „parametr” wewnątrz metody anonimowej, wyrażenia lambda lub wyrażenia zapytania

Oto prosty przykład:

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
źródło
Chodzi o iteratory, ale większość tego samego rozumowania w tym poście (również autorstwa Erica Lipperta & mdash; w końcu jest w zespole projektowym języka) dotyczy lambd : < blogs.msdn.com/ericlippert/archive/2009/07/13 /… >
Joel Coehoorn
17
Czy mogę zapytać, jakie obejście zostało znalezione?
Beatles1692
3
Możesz po prostu zadeklarować lokalną normalną zmienną i pracować z nią, a następnie przypisać wynik do value ... Dodaj var tempValue = value; a następnie pracuj z tempValue.
Drunken Code Monkey

Odpowiedzi:

122

Lambdy wydają się zmieniać czas życia przechwytywanych zmiennych. Na przykład poniższe wyrażenie lambda powoduje, że parametr p1 żyje dłużej niż bieżąca ramka metody, ponieważ do jego wartości można uzyskać dostęp, gdy ramka metody nie znajduje się już na stosie

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

Inną właściwością przechwyconych zmiennych jest to, że zmiany w zmiennej są również widoczne poza wyrażeniem lambda. Na przykład następujące wydruki 42

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

Te dwie właściwości dają pewien zestaw efektów, które są sprzeczne z parametrem ref w następujący sposób

  • parametry ref mogą mieć ustaloną żywotność. Rozważ przekazanie zmiennej lokalnej jako parametru ref do funkcji.
  • Efekty uboczne lambda musiałyby być widoczne w samym parametrze ref. Zarówno w metodzie, jak iw obiekcie wywołującym.

Są to nieco niezgodne właściwości i jeden z powodów, dla których są one niedozwolone w wyrażeniach lambda.

JaredPar
źródło
36
Rozumiem, że nie możemy użyć refwyrażenia lambda, ale chęć jego użycia nie została zaspokojona.
zionpi
85

Pod maską metoda anonimowa jest implementowana przez podnoszenie przechwyconych zmiennych (o to właśnie chodzi w treści pytania) i przechowywanie ich jako pól klasy wygenerowanej przez kompilator. Nie ma możliwości zapisania parametru reflub outjako pola. Eric Lippert omówił to we wpisie na blogu . Zwróć uwagę, że istnieje różnica między przechwyconymi zmiennymi a parametrami lambda. Państwo może mieć „parametrów formalnych” jak poniżej, ponieważ są one nie złapanych zmienne:

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
źródło
70

Możesz, ale musisz jawnie zdefiniować wszystkie typy tak

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

Jest jednak nieważne

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

Jest ważna

Ben Adams
źródło
13
To robi; pytanie brzmi: dlaczego nie możesz; odpowiedź brzmi: możesz.
Ben Adams
24
Tak nie jest; Pytanie brzmi, dlaczego nie możesz odwołać się do istniejącej zmiennej , już zdefiniowanej reflub outwewnątrz lambdy. Jest to jasne, jeśli przeczytasz przykładowy kod (spróbuj ponownie przeczytać go ponownie). Przyjęta odpowiedź jasno wyjaśnia dlaczego. Twoja odpowiedź dotyczy użycia parametruref lub do lambda. Zupełnie nie odpowiadając na pytanie i mówiąc o czymś innymout
edc65
4
@ edc65 ma rację ... nie ma to nic wspólnego z tematem pytania, którym jest treść wyrażenia lamba (po prawej), a nie jego lista parametrów (po lewej). To dziwne, że otrzymało 26 głosów pozytywnych.
Jim Balter,
6
Pomogło mi to jednak. +1 za to. Dzięki
Emad,
1
Ale nadal nie rozumiem, dlaczego został tak zaprojektowany. Dlaczego muszę jawnie definiować wszystkie typy? Semantycznie nie muszę. Czy coś tracę?
joe
5

Ponieważ jest to jeden z najlepszych wyników dla „C # ref lambda” w Google; Czuję, że muszę rozwinąć powyższe odpowiedzi. Starsza (C # 2.0) składnia anonimowego delegata działa i obsługuje bardziej złożone podpisy (a także zamknięcia). Lambda i anonimowi delegaci przynajmniej współdzielili postrzeganą implementację w zapleczu kompilatora (jeśli nie są identyczni) - i co najważniejsze, obsługują zamknięcia.

Co próbowałem zrobić, gdy szukałem, aby zademonstrować składnię:

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

Pamiętaj tylko, że Lambdy są proceduralnie i matematycznie bezpieczniejsze (ze względu na wspomnianą wcześniej promocję wartości referencyjnej): możesz otworzyć puszkę robaków. Pomyśl uważnie, używając tej składni.

Jonathan Dickinson
źródło
3
Myślę, że źle zrozumiałeś pytanie. Pytanie brzmiało, dlaczego lambda nie może uzyskać dostępu do zmiennych ref / out w swojej metodzie kontenera, a nie dlaczego sama lambda nie może zawierać zmiennych ref / out. AFAIK nie ma ku temu dobrego powodu. Dzisiaj napisałem lambdę (a, b, c, ref d) => {...}i zostałem refpodkreślony na czerwono z komunikatem o błędzie „Parametr '4' musi być zadeklarowany za pomocą słowa kluczowego 'ref'”. Facepalm! PS co to jest „promocja wartości referencyjnej”?
Qwertie
1
@Qwertie Mam to do pracy z pełną parametryzacją, co oznacza, że ​​zawiera typy na a, b, c i d i działa. Zobacz odpowiedź BenAdamsa (choć on też źle rozumie pierwotne pytanie).
wyd Bayiates
@Qwertie Myślę, że usunąłem tylko połowę tego punktu - myślę, że pierwotnie chodziło o to, że umieszczanie parametrów ref w zamknięciu może być ryzykowne, ale musiałem później zdać sobie sprawę, że tak się nie dzieje w przykładzie, który podałem (i nie robię tego Wiem, czy to by się w ogóle skompilowało).
Jonathan Dickinson
Nie ma to nic wspólnego z faktycznie zadanym pytaniem ... zobacz zaakceptowaną odpowiedź i komentarze pod odpowiedzią Bena Adamsa, który również źle zrozumiał pytanie.
Jim Balter
1

A może to?

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();
}
łaskawy
źródło