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


173

Чому ви не можете використовувати параметр ref або out у лямбда-виразі?

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

CS1628 : Неможливо використовувати параметр 'параметр' у параметрі відмови чи виходу всередині анонімного методу, лямбда-виразу чи виразу запиту

Ось простий приклад:

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

Йдеться про ітераторів, але багато з тих же міркувань у цій публікації (також Ерік Ліпперт & mdash; він врешті є членом команди дизайнера мов) стосується лямбда: < blogs.msdn.com/ericlippert/archive/2009/07/13 /… >
Joel Coehoorn

17
Чи можу я запитати, що було вирішено, що ви знайшли?
Бітлз1692

3
Ви можете просто оголосити локальну звичайну змінну і попрацювати з нею, а потім призначити результат значенням ... Додати var tempValue = значення; а потім працюйте з 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;
        };
}

Пам’ятайте лише про те, що лямбдас є безпечнішими з точки зору процедур та математики (через вищезгадане просування посилань): ви можете відкрити балончиків. Ретельно подумайте, використовуючи цей синтаксис.


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

1
@Qwertie Я отримав це, щоб працювати з повною параметризацією, тобто включати типи на a, b, c і d, і це працює. Дивіться відповідь BenAdams (хоча він і неправильно розуміє оригінальне запитання).
Ед Байятес

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

Це не має нічого спільного з фактично заданим питанням ... див. Прийняту відповідь та коментарі під відповіддю Бен Адамс, який також неправильно зрозумів питання.
Джим Балтер

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.