Подтвердить что ты не робот

Почему анонимные делегаты /lambdas не вызывают типы out/ref parameters?

Несколько вопросов С# в StackOverflow спросят, как сделать анонимных делегатов /lambdas с параметрами out или ref. См. Например:

Для этого вам просто нужно указать тип параметра, например:

public void delegate D(out T p);
// ...
D a = (out T t) => { ... };      // Lambda syntax.
D b = delegate(out T t) { ... }; // Anonymous delegate syntax.

Мне интересно, почему тип явно требуется. Есть ли особая причина в том, что это так? То есть, с точки зрения компилятора/языка, почему не разрешено следующее?

D a = (out t) => { ... };      // Lambda syntax -- implicit typing.
D b = delegate(out t) { ... }; // Anonymous delegate syntax -- implicit typing.

или даже лучше, просто:

D a = (t) => { ... };      // Lambda syntax -- implicit typing and ref|out-ness.
D b = delegate(t) { ... }; // Anonymous delegate syntax -- implicit typing and ref|out-ness.
4b9b3361

Ответ 1

Интересный вопрос.

Сначала рассмотрим разницу между анонимными методами и лямбдами. С точки зрения писателя компилятора наиболее важным отличием является то, что lambdas может потребовать от компилятора указать тип параметров из целевого объекта, которому назначается лямбда; У анонимных методов С# 2 эта функция отсутствует. Эта функция кажется небольшой разницей, но на самом деле она имеет серьезные последствия для реализации компилятора. См. Мою серию блога на эту тему для некоторых мыслей о том, почему это:

http://blogs.msdn.com/ericlippert/archive/2007/01/10/lambda-expressions-vs-anonymous-methods-part-one.aspx

Итак, теперь давайте приступим к вашему фактическому вопросу: почему мы не можем выводить оттуда /refness от целевого типа к параметрам лямбда. То есть, если мы делегируем void D (out int x), то, конечно, D d = x = > {x = 10; } можно сделать вывод, что x является "out int".

Нет никакой технической причины, я знаю, почему мы не могли этого сделать. Внутренне в компиляторе типы out/ref представлены как типы, подобные любым другим.

Однако функции не могут быть выполнены только потому, что они могут быть выполнены; они делаются, потому что есть веские основания для этого. Для lambdas неотразимая причина делать вывод типа в первую очередь - LINQ; мы хотим иметь возможность сделать простое синтаксическое преобразование при понимании запроса в вызове метода с помощью lambdas, и пусть механизм вывода типа метода задает типы всех параметров лямбда. Ни один из созданных методов LINQ не имеет делегатов с параметрами out или ref.

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

С# 3 был "длинным полюсом" в расписании Visual Studio; у нас было самое большое количество дней работы, запланированных для любой команды, которая отправляет компонент в VS. Это означало, что каждый день мы списывали расписание, все подразделение поскользнулось. Это было мощным препятствием для того, чтобы тратить время на ненужные функции, которые никому не приносили пользу. Таким образом, работа не была выполнена.

Я согласен с тем, что было бы неплохо быть более последовательным здесь, но это вряд ли произойдет. У нас много приоритетов.

Ответ 2

От комментарий Эрика Липперта о том, почему объявление и назначение переменной var нельзя разделить:

Я согласен с тем, что в принципе это можно сделать, но на практике это будет более сложным, чем указывает ваш быстрый эскиз. var требует не только инициализатора, но также требует, чтобы инициализатор не ссылался на переменную. Если у вас есть int M (out int), вы можете сказать "int x = M (out x)"; но вы не можете сказать "var x = M (out x)"; потому что для разрешения перегрузки на M нам нужно знать тип x, который мы пытаемся выяснить. Было бы законным говорить "var s, if (b) M (out s), иначе s = 0;

Я бы предположил, что ответ на ваш вопрос схож, учитывая, например,

D a = (out var x) => x = M(out x);