У меня есть следующая функция для получения ошибок валидации для карточки. Мой вопрос связан с работой с 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
без необходимости их перечисления?
Думаю, это, вероятно, глупый вопрос, но я хочу убедиться, что не ошибаюсь.
Это определенно не глупый вопрос, и это то, что F# поддерживает с yield!
для целой коллекции против yield
для отдельного элемента. (Это может быть очень полезно в плане хвостовой рекурсии...)
К сожалению, это не поддерживается в 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<П>>` чтобы сделать этот код Сохранить отложенное выполнение. Я'м вентилятор отсрочки исполнения по многим причинам, одна из них заключается в том, что объем памяти невелик даже для огромных-mongous enumerables.
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
и развернуть
в callsite.