Доходность вложенных возвращение с интерфейс IEnumerable
у меня есть следующая функция, чтобы получить ошибки проверки на карту. Мой вопрос касается работы с GetErrors. Оба метода имеют одинаковый тип возврата IEnumerable<ErrorInfo>
.
private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
var errors = GetMoreErrors(card);
foreach (var e in errors)
yield return e;
// further yield returns for more validation errors
}
можно ли вернуть все ошибки в GetMoreErrors
без необходимости перечислять через них?
думая об этом, это наверное глупый вопрос, но я хочу убедиться, что я не ошибся.
6 ответов:
это определенно не глупый вопрос, и это то, что F# поддерживает с
yield!
для всей коллекции vsyield
для одного элемента. (Это может быть очень полезно с точки зрения хвостовой рекурсии...)к сожалению, он не поддерживается в C#.
однако, если у вас есть несколько методов, каждый из которых возвращает
IEnumerable<ErrorInfo>
, вы можете использоватьEnumerable.Concat
чтобы сделать код проще:private static IEnumerable<ErrorInfo> GetErrors(Card card) { return GetMoreErrors(card).Concat(GetOtherErrors()) .Concat(GetValidationErrors()) .Concat(AnyMoreErrors()) .Concat(ICantBelieveHowManyErrorsYouHave()); }
есть одно очень важное различие между двумя реализациями хотя: этот вызовет все методы тут, хотя он будет использовать только возвращенные итераторы по одному. Ваш существующий код будет ждать, пока он не пройдет через все в
GetMoreErrors()
прежде, чем просит о следующих ошибках.обычно это не важно, но стоит понимать, что произойдет, когда.
вы можете настроить все источники ошибок, как это (имена методов, заимствованные из ответа Джона Скита).
private static IEnumerable<IEnumerable<ErrorInfo>> GetErrorSources(Card card) { yield return GetMoreErrors(card); yield return GetOtherErrors(); yield return GetValidationErrors(); yield return AnyMoreErrors(); yield return ICantBelieveHowManyErrorsYouHave(); }
затем вы можете перебирать их одновременно.
private static IEnumerable<ErrorInfo> GetErrors(Card card) { foreach (var errorSource in GetErrorSources(card)) foreach (var error in errorSource) yield return error; }
в качестве альтернативы вы можете сгладить источники ошибок с помощью
SelectMany
.private static IEnumerable<ErrorInfo> GetErrors(Card card) { return GetErrorSources(card).SelectMany(e => e); }
выполнение методов в
GetErrorSources
будет слишком затягивается.
Я придумал быстрый
yield_
фрагмент:вот фрагмент XML:
<?xml version="1.0" encoding="utf-8"?> <CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"> <CodeSnippet Format="1.0.0"> <Header> <Author>John Gietzen</Author> <Description>yield! expansion for C#</Description> <Shortcut>yield_</Shortcut> <Title>Yield All</Title> <SnippetTypes> <SnippetType>Expansion</SnippetType> </SnippetTypes> </Header> <Snippet> <Declarations> <Literal Editable="true"> <Default>items</Default> <ID>items</ID> </Literal> <Literal Editable="true"> <Default>i</Default> <ID>i</ID> </Literal> </Declarations> <Code Language="CSharp"><![CDATA[foreach (var $i$ in $items$) yield return $i$$end$;]]></Code> </Snippet> </CodeSnippet> </CodeSnippets>
Я не вижу ничего плохого в вашей функции, я бы сказал, что она делает то, что вы хотите.
подумайте о том, что Yield возвращает элемент в конечном перечислении каждый раз, когда он вызывается, поэтому, когда он находится в цикле foreach, каждый раз, когда он вызывается, он возвращает 1 элемент. У вас есть возможность поместить условные операторы в свой foreach для фильтрации результирующего набора. (просто не уступая по вашим критериям исключения)
Если вы добавляете последующие урожаи позже в методе он продолжит добавлять 1 элемент в перечисление, что позволит делать такие вещи, как...
public IEnumerable<string> ConcatLists(params IEnumerable<string>[] lists) { foreach (IEnumerable<string> list in lists) { foreach (string s in list) { yield return s; } } }
да можно вернуть все ошибки сразу. Просто вернуть
List<T>
илиReadOnlyCollection<T>
.возвращая
IEnumerable<T>
вы возвращаете последовательность чего-то. На поверхности это может показаться идентичным возвращению коллекции, но есть ряд отличий, которые вы должны иметь в виду.коллекции
- вызывающий может быть уверен, что и коллекция, и все элементы будут существовать, когда коллекция будет возвращена. Если коллекция должен быть создан за вызов, возвращая коллекцию-это действительно плохая идея.
- большинство коллекций могут быть изменены при возврате.
- коллекция имеет конечный размер.
последовательности
- можно перечислить - и это почти все, что мы можем сказать наверняка.
- возвращенная последовательность сама по себе не может быть изменена.
- каждый элемент может быть создан как часть выполнения последовательности (т. е. возврат
IEnumerable<T>
позволяет для ленивой оценки, возвращаяList<T>
нет).- последовательность может быть бесконечной и, таким образом, оставить его вызывающему, чтобы решить, сколько элементов должно быть возвращено.
Я удивлен, что никто не подумал рекомендовать простой метод расширения на
IEnumerable<IEnumerable<T>>
чтобы этот код сохранил свое отложенное выполнение. Я поклонник отложенного выполнения по многим причинам, одна из них заключается в том, что объем памяти невелик даже для огромных монгольских перечислений.public static class EnumearbleExtensions { public static IEnumerable<T> UnWrap<T>(this IEnumerable<IEnumerable<T>> list) { foreach(var innerList in list) { foreach(T item in innerList) { yield return item; } } } }
и вы могли бы использовать его в вашем случае, как это
private static IEnumerable<ErrorInfo> GetErrors(Card card) { return DoGetErrors(card).UnWrap(); } private static IEnumerable<IEnumerable<ErrorInfo>> DoGetErrors(Card card) { yield return GetMoreErrors(card); // further yield returns for more validation errors }
аналогично, вы можете покончить с функцией обертки вокруг
DoGetErrors
иUnWrap
в callsite.