Почему вы не можете использовать урожай в лямбда, когда можете использовать ожидание в лямбда?

По словам Эрика Липперта, анонимные итераторы не были добавлены к языку, потому что было бы слишком сложно реализовать его.

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

Может кто-то пролить свет на это?

Ответ 1

Согласно Эрику Липперту, анонимные итераторы не были добавлены к языку, потому что было бы слишком сложно реализовать его.

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

Компилятор должен сделать то же самое для async-методов, как и для итераторов (преобразовать их в конечные машины), поэтому я очень смущен, почему анонимные итераторы также недопустимы, когда используются анонимные методы async.

Краткая история имеет значение. Сначала на С# были анонимные методы и блоки итераторов в С# 2.0. Когда я добавил lambdas в С# 3.0, это была большая стоимость для реорганизации всего существующего кода анонимного метода, чтобы он мог обрабатывать все новые функции lambdas. Это сделало его еще более сложным и дорогостоящим. Создание блока итератора lambdas было оценено слишком дорого для преимуществ, которые будут начислены; это был бы большой процент от общей стоимости. Мы не могли себе это позволить.. Если вы добавили каждую команду в расписание работы отдела разработчиков, команда с "самым длинным полюсом" была командой компилятора С# 3.0, и моя работа над семантическим анализатором была IIRC самый длинный полюс в команде компилятора. Каждый день мы, возможно, пропустили С# 3.0, это был бы тот день, когда Visual Studio поскользнулась. Поэтому все, что не делало LINQ лучше, было разрезано, и это включало итератор lambdas.

В С# 4 итератор лямбда был одной из многих особенностей, которые были рассмотрены. У нас был список потенциальных хороших функций буквально дольше, чем у вас, и мы могли позволить себе делать менее десятой части.

В С# 5 команда добавила асинхронные методы. Группы проектирования и внедрения долгое время пытались найти базовую абстракцию, которая была общей для блока итератора и ожидала перезаписывания; они, очевидно, похожи, как вы заметили. Но, в конечном счете, стоимость поиска общего решения не оплачивалась сама по себе. Общепринятость на удивление дорогостоящая, и нахождение общности в том, что по дизайну объединяет только две вещи, глупо, если это не дешево.

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

И снова у нас проблема с длинными полюсами. Любые работы с лямбда-движком, которые потенциально могут быть дестабилизированы await, следует избегать, и это включает попытку заставить их работать с блоками итератора.

Теперь сравните Visual Basic. У VB долгое время не было блоков итераторов. Когда они были добавлены, не было существующей инфраструктуры, чтобы продолжать работать! Все это можно было бы построить с нуля, чтобы обрабатывать блоки итератора, содержащие лямбды и лямбда, содержащие блоки итераторов, и так было сделано.

Компилятор С# был тщательно зашифрован и переписан через проект Roslyn. Я надеюсь, что это снизит стоимость реализации блока итератора lambdas в гипотетической будущей версии С#. Мы увидим!

Ответ 2

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

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

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

Ответ 3

Посмотрите на этот код (это не работает, просто пример):

Task<IEnumerable<int>> resultTask = new Task<IEnumerable<int>>(() =>
{
    for (int i = 0; i < 10; ++i)
    {
        yield return i;
    }
});

Разве вы не находите его каким-то неструктурированным?

Предполагая, что весь диапазон использования лямбда, это было бы так тяжело и не стоило бы справляться с yield "лень" должным образом.

Однако существуют большие подходы к возврату yield из параллельных задач.

Но давайте посмотрим на следующее. Определение метода с yield return:

static IEnumerable<int> GetIntegers()
{
    for (int i = 0; i < 10; ++i)
    {
        yield return i;
    }
}

И положив его в lambda будет работа:

Task<IEnumerable<int>> resultTask = new Task<IEnumerable<int>>(() =>
{
    return GetIntegers();
});

Как будет вести себя этот код? Это потеряет реальные преимущества yield?