Возврат IEnumerable<T> из yield в C#

я умею компилировать

public async Task<IEnumerable<T>> GetResultsFromQueryExecutionId<T>(string queryExecutionId)
{
     await using var csvResponseStream = await transferUtility.OpenStreamAsync("bucket", "blah.csv");
     return GetResultsFromResponseStream<T>(csvResponseStream);
}

private IEnumerable<T> GetResultsFromResponseStream<T>(Stream csvResponseStream)
{
     using var streamReader = new StreamReader(csvResponseStream);
     using var csvReader = new CsvReader(streamReader, csvConfiguration);
     foreach (var record in csvReader.GetRecords<T>())
     {
          yield return record;
     }
}

но если я попытаюсь удалить частный метод и запустить код в одном методе, я получаю ошибку компиляции: «Тело« GetResultsFromQueryExecutionId »не может быть блоком итератора, потому что« System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable >' не является типом интерфейса асинхронного итератора"

Я пытался

public async Task<IEnumerable<T>> GetResultsFromQueryExecutionId<T>(string queryExecutionId, string logPrefix = null)
{
     await using var csvResponseStream = await transferUtility.OpenStreamAsync("bucket", "blah.csv");
     using var streamReader = new StreamReader(csvResponseStream);
     using var csvReader = new CsvReader(streamReader, csvConfiguration);
     foreach (var record in csvReader.GetRecords<T>())
     {
          yield return record;
     }
}

Я ожидал, что код будет эквивалентен. Кто-нибудь может объяснить, почему компилятор не позволяет мне объединить это в один метод, пожалуйста?

yield return предназначен только для типов возврата IEnumerable<T> или IAsyncEnumerable<T>. Попробуйте переписать на IAsyncEnumerable

Ryan 14.07.2023 17:35

Спасибо, Райан. Вы видите какие-либо проблемы с моей первой реализацией, даже если она компилируется?

gregdev 14.07.2023 17:40

Зависит от того, что вы подразумеваете под «проблемой». Результат будет обработан лениво, поэтому вы можете получить неожиданные результаты. Например, если вы вызовете GetResultsFromQueryExecutionId, а затем удалите CSV-файл перед повторением результатов, вы получите сообщение об ошибке, когда перейдете к использованию результатов.

StriplingWarrior 14.07.2023 17:43

Я бы проверил это, чтобы убедиться, что ваш поток к источнику данных удаляется сразу после того, как вы получите от него IEnumerable. Т.е. источник данных всегда теряется. Я бы переписал GetResultsFromQueryExecutionId, передав поток в качестве параметра, чтобы контролировать существование этого источника данных.

Ryan 14.07.2023 17:49

Хорошо, спасибо, я проверю, могу ли я получить GetResultsFromQueryExecutionId с моей скомпилированной реализацией. (Я думаю, что IAsyncEnumerable - это то, что мне действительно нужно, я был просто удивлен, что не смог объединить код, я думаю, что это особый случай, поскольку С# обычно допускает такие вещи с другими типами - может быть, со всеми другими типами....)

gregdev 14.07.2023 17:58
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
5
51
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

yield return — это особая конструкция, которая заставляет метод, в котором она находится, компилироваться в совершенно другом режиме. Вы можете возвращать IAsyncEnumerable<> или IEnumerable<> (или типы с похожими методами) только из методов, которые его используют, поэтому, если вы хотите вернуть что-то другое, вам нужно будет разбить код на отдельные методы, как у вас есть.

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

public async Task<IEnumerable<T>> GetResultsFromQueryExecutionId<T>(string queryExecutionId)
{
    await Task.Yield();
    return GetResultsFromResponseStream();
    IEnumerable<T> GetResultsFromResponseStream()
    {
        foreach (var record in new T[0] )
        {
            yield return record;
        }
    }
}

Вместо этого может быть уместно вернуть IAsyncEnumerable<T>:

public async IAsyncEnumerable<T> GetResultsFromQueryExecutionId<T>(string queryExecutionId)
{
    await Task.Yield();
    foreach (var record in new T[0] )
    {
        yield return record;
    }
}

Это изменит то, как люди используют ваш метод.

// Before
var results = await GetResultsFromQueryExecutionId<int>("foo");
foreach(var result in results)
{
    ...
}
// After 
var results = GetResultsFromQueryExecutionId<int>("foo");
await foreach (var result in results)
{
    ...
}

«Вы можете вернуть только IAsyncEnumerable<> или IEnumerable<>» — на самом деле не совсем верно =)

Guru Stron 14.07.2023 17:54

Спасибо @StriplingWarrior. Я согласен с тем, что возвращение IAsyncEnumerable<T> — это то, что мне действительно нужно, однако я был удивлен, что не смог объединить два метода в своей работе. Я полагаю, что «доходность — это очень особенная конструкция» покрывает это.

gregdev 14.07.2023 17:56

Не фанат использования await Task.Yield() для разгрузки. Мои аргументы в комментариях этого ответа Марка Гравелла.

Theodor Zoulias 14.07.2023 18:17

@GuruStron, чтение спецификаций совершенно нормально для точных рассуждений, но семантически имеют значение только те * Enumerable типы.

Ryan 14.07.2023 18:22

@TheodorZoulias: Дело было не в этом. Я упрощал, чтобы использовать код, который компилируется при использовании асинхронных шаблонов, без необходимости имитировать все методы, которые использует OP.

StriplingWarrior 14.07.2023 18:23

@GuruStron: Да, иди и разберись со мной. :-) Я обновил формулировку с оговоркой. Если это все еще неточно, не стесняйтесь редактировать мой ответ.

StriplingWarrior 14.07.2023 18:25

Для меня любой ответ, который продвигает await Task.Yield() как механизм разгрузки, недостоверен. Если намерение состоит в том, чтобы имитировать асинхронную работу, я бы предложил await Task.Delay(1000); // Simulate asynchronous work.

Theodor Zoulias 14.07.2023 18:27

@TheodorZoulias: я не чувствовал, что это продвигает использование await Task.Yield() для разгрузки, так же как и ваше предложение не способствует использованию ненужных Task.Delay. Если вам от этого станет лучше, вы можете добавить комментарий // Simulate asynchronous work, но я, честно говоря, думаю, что вы немного уперты в этом.

StriplingWarrior 14.07.2023 18:32

Действительно, у меня есть сильные чувства против использования Task.Yield для чего-либо, что не является его предполагаемой целью, в которой я даже не уверен, что это такое. Если вы действительно хотите оставить это в ответе, просто проголосуйте против и двигайтесь дальше. По большому счету, один голос за или против не имеет большого значения. :-)

Theodor Zoulias 14.07.2023 18:41

Как написано в спецификации:

Оператор yield используется в блоке итератора (§13.3), чтобы передать значение объекту перечислителя (§15.14.5) или перечислимому объекту (§15.14.6) итератора или сигнализировать об окончании итерации.

В настоящее время, насколько мне известно, компилятор поддерживает возврат следующих типов из методов, использующих yield return:

IEnumerable
IEnumerator
IEnumerable<T>
IEnumerator<T>
IAsyncEnumerable<T>
IAsyncEnumerator<T>

И Task<IEnumerable<T>> не является одним из них, поэтому вы ограничены либо использованием подхода с двумя методами, либо использованием IAsyncEnumerable/IAsyncEnumerator:

private async IAsyncEnumerable<T> GetResultsFromResponseStream<T>(Stream csvResponseStream)
{
    await using var csvResponseStream = ... 
    // the iterator block
}

Хотя лично я бы воспользовался первым (для удобства можно превратить метод в локальную функцию).

Читать далее:

Спасибо, Гуру Строн, очень признателен.

gregdev 14.07.2023 18:02

@gregdev был рад помочь! Не стесняйтесь принимать и голосовать за любой ответ, который был полезен!

Guru Stron 14.07.2023 18:04

Другие вопросы по теме