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

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

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

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

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

Ответ 1

Lambdas имеет вид изменения времени жизни переменных, которые они захватывают. Например, следующее лямбда-выражение заставляет параметр 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. Как внутри метода, так и в вызывающем.

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

Ответ 2

Под капотом анонимный метод реализуется путем подъема захваченных переменных (что и есть вопрос вашего тела вопроса) и сохранения их в качестве полей генерируемого компилятором класса. Невозможно сохранить параметр 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);
}

Ответ 3

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

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

Недопустимо, однако

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

Действителен

Ответ 4

Как это один из лучших результатов для "С# lambda ref" в Google; Я чувствую, что мне нужно расширить приведенные выше ответы. Более старый (С# 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): вы можете открыть банку червей. Подумайте внимательно, используя этот синтаксис.