Невозможно использовать параметр 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): вы можете открыть банку червей. Подумайте внимательно, используя этот синтаксис.