я умею компилировать
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;
}
}
Я ожидал, что код будет эквивалентен. Кто-нибудь может объяснить, почему компилятор не позволяет мне объединить это в один метод, пожалуйста?
Спасибо, Райан. Вы видите какие-либо проблемы с моей первой реализацией, даже если она компилируется?
Зависит от того, что вы подразумеваете под «проблемой». Результат будет обработан лениво, поэтому вы можете получить неожиданные результаты. Например, если вы вызовете GetResultsFromQueryExecutionId, а затем удалите CSV-файл перед повторением результатов, вы получите сообщение об ошибке, когда перейдете к использованию результатов.
Я бы проверил это, чтобы убедиться, что ваш поток к источнику данных удаляется сразу после того, как вы получите от него IEnumerable. Т.е. источник данных всегда теряется. Я бы переписал GetResultsFromQueryExecutionId, передав поток в качестве параметра, чтобы контролировать существование этого источника данных.
Хорошо, спасибо, я проверю, могу ли я получить GetResultsFromQueryExecutionId с моей скомпилированной реализацией. (Я думаю, что IAsyncEnumerable - это то, что мне действительно нужно, я был просто удивлен, что не смог объединить код, я думаю, что это особый случай, поскольку С# обычно допускает такие вещи с другими типами - может быть, со всеми другими типами....)





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<>» — на самом деле не совсем верно =)
Спасибо @StriplingWarrior. Я согласен с тем, что возвращение IAsyncEnumerable<T> — это то, что мне действительно нужно, однако я был удивлен, что не смог объединить два метода в своей работе. Я полагаю, что «доходность — это очень особенная конструкция» покрывает это.
Не фанат использования await Task.Yield() для разгрузки. Мои аргументы в комментариях этого ответа Марка Гравелла.
@GuruStron, чтение спецификаций совершенно нормально для точных рассуждений, но семантически имеют значение только те * Enumerable типы.
@TheodorZoulias: Дело было не в этом. Я упрощал, чтобы использовать код, который компилируется при использовании асинхронных шаблонов, без необходимости имитировать все методы, которые использует OP.
@GuruStron: Да, иди и разберись со мной. :-) Я обновил формулировку с оговоркой. Если это все еще неточно, не стесняйтесь редактировать мой ответ.
Для меня любой ответ, который продвигает await Task.Yield() как механизм разгрузки, недостоверен. Если намерение состоит в том, чтобы имитировать асинхронную работу, я бы предложил await Task.Delay(1000); // Simulate asynchronous work.
@TheodorZoulias: я не чувствовал, что это продвигает использование await Task.Yield() для разгрузки, так же как и ваше предложение не способствует использованию ненужных Task.Delay. Если вам от этого станет лучше, вы можете добавить комментарий // Simulate asynchronous work, но я, честно говоря, думаю, что вы немного уперты в этом.
Действительно, у меня есть сильные чувства против использования Task.Yield для чего-либо, что не является его предполагаемой целью, в которой я даже не уверен, что это такое. Если вы действительно хотите оставить это в ответе, просто проголосуйте против и двигайтесь дальше. По большому счету, один голос за или против не имеет большого значения. :-)
Как написано в спецификации:
Оператор
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 был рад помочь! Не стесняйтесь принимать и голосовать за любой ответ, который был полезен!
yield return предназначен только для типов возврата
IEnumerable<T>илиIAsyncEnumerable<T>. Попробуйте переписать наIAsyncEnumerable