Невозможно использовать параметр ref или out в лямбда-выражениях


173

Почему вы не можете использовать параметр ref или out в лямбда-выражении?

Сегодня я столкнулся с ошибкой и нашел обходной путь, но мне все еще было любопытно, почему это ошибка времени компиляции.

CS1628 : невозможно использовать в ref или out параметр 'parameter' внутри анонимного метода, лямбда-выражения или выражения запроса

Вот простой пример:

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

Речь идет об итераторах, но большая часть тех же рассуждений в этом посте (также Эрика Липперта - в конце концов, он работает в команде языкового дизайна) относится к лямбдам: < blogs.msdn.com/ericlippert/archive/2009/07/13 /… >
Джоэл Коухорн

17
Могу я спросить, какой обходной путь вы нашли?
Beatles1692

3
Вы можете просто объявить локальную нормальную переменную и поработать с ней, а затем присвоить результат значению ... Добавить var tempValue = value; а затем работать с tempValue.
Пьяный Код Обезьяны

Ответы:


122

Лямбды имеют вид изменения времени жизни переменных, которые они захватывают. Например, следующее лямбда-выражение заставляет параметр p1 жить дольше, чем текущий фрейм метода, так как к его значению можно получить доступ после того, как фрейм метода больше не находится в стеке.

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

Другое свойство захваченных переменных заключается в том, что изменения в переменной также видны вне лямбда-выражения. Например, следующие отпечатки 42

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

Эти два свойства производят определенный набор эффектов, которые сталкиваются с параметром ref следующими способами

  • Параметры ref могут иметь фиксированное время жизни. Рассмотрите возможность передачи локальной переменной в качестве параметра ref функции.
  • Побочные эффекты в лямбде должны быть видны на самом параметре ref. И в методе, и в вызывающем.

Это несколько несовместимые свойства, и они являются одной из причин, по которой они запрещены в лямбда-выражениях.


36
Я понимаю, что мы не можем использовать refвнутри лямбда-выражения, но желание использовать его не было удовлетворено.
Zionpi

85

Под капотом анонимный метод реализуется путем подъема захваченных переменных (о чем и состоит ваше тело вопроса) и сохранения их в виде полей сгенерированного компилятором класса. Нет способа сохранить параметр refили outкак поле. Эрик Липперт обсуждал это в записи в блоге . Обратите внимание, что есть разница между захваченными переменными и лямбда-параметрами. Вы можете иметь «формальные параметры», подобные следующим, поскольку они не являются захваченными переменными:

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

Вы можете, но вы должны явно определить все типы так

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

Неверно, однако

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

Действует


13
Оно делает; вопрос почему ты не можешь; ответ вы можете.
Бен Адамс

24
Это не так; Вопрос в том, почему вы не можете ссылаться на существующую переменную , уже определенную refили outвнутри лямбда-выражения. Понятно, если вы читаете пример кода (попробуйте еще раз, чтобы прочитать его снова). Принятый ответ четко объясняет почему. Ваш ответ об использовании refили out параметре лямбда. Совершенно не отвечая на вопрос и говоря о чем-то другом
edc65

4
@ edc65 прав ... это не имеет ничего общего с предметом вопроса, который касается содержания выражения lamba (справа), а не его списка параметров (слева). Это странно, что это получило 26 голосов.
Джим Балтер

6
Это помогло мне, хотя. +1 за это. Спасибо
Эмад

1
Но я все еще не понимаю, почему это было разработано, чтобы быть таким. Почему я должен явно определять все типы? Семантически мне не нужно. Я что-то теряю?
Джо

5

Так как это один из лучших результатов для "C # lambda ref" в Google; Я чувствую, что мне нужно расширить эти ответы. Старый (C # 2.0) синтаксис анонимного делегата работает и поддерживает более сложные подписи (а также замыкания). Лямбда и анонимные делегаты, по крайней мере, имеют общую воспринимаемую реализацию в бэкенде компилятора (если они не идентичны) - и, самое главное, они поддерживают замыкания.

Что я пытался сделать, когда делал поиск, чтобы продемонстрировать синтаксис:

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

Просто имейте в виду, что Lambdas процедурно и математически более безопасны (из-за упомянутой выше рекламы ref value): вы можете открыть банку с червями. Тщательно продумайте этот синтаксис.


3
Я думаю, что вы неправильно поняли вопрос. Вопрос был в том, почему лямбда не может получить доступ к переменным ref / out в своем методе контейнера, а не в том, почему сама лямбда не может содержать переменные ref / out. AFAIK нет веских причин для последнего. Сегодня я написал лямбду (a, b, c, ref d) => {...}и refбыл подчеркнут красным с сообщением об ошибке «Параметр« 4 »должен быть объявлен с ключевым словом ref». Facepalm! PS что такое "продвижение стоимости ref"?
Qwertie

1
@Qwertie Я получил это, чтобы работать с полной параметризацией, то есть включать типы на a, b, c и d, и это работает. Смотрите ответ БенАдамса (хотя он также неправильно понимает исходный вопрос).
Эд Байятес

@Qwertie Я думаю, что я удалил только половину этого пункта - я думаю, что первоначальная точка зрения заключалась в том, что помещение ref-параметров в замыкание может быть рискованным, но я, должно быть, впоследствии понял, что этого не произошло в примере, который я дал (и не делаю Я знаю, будет ли это даже компилировать).
Джонатан Дикинсон

Это не имеет ничего общего с фактически заданным вопросом ... см. Принятый ответ и комментарии под ответом Бена Адамса, который также неправильно понял вопрос.
Джим Балтер

1

А может это?

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();
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.