Problem
To get validation errors for a card, I have the following function. My inquiry is about handling GetErrors. The result type of both methods is IEnumerableErrorInfo>.
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
}
Is it feasible to have GetMoreErrors return all of the errors without having to list them?
Asked by John Oxley
Solution #1
It’s something F# encourages with yield! yield for the entire collection vs. yield for a single item (This is particularly handy in the context of tail recursion…)
Regrettably, C# does not support it.
You can use Enumerable if you have numerous methods that each return an IEnumerableErrorInfo>. To make your code easier to read, use concat:
private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
return GetMoreErrors(card).Concat(GetOtherErrors())
.Concat(GetValidationErrors())
.Concat(AnyMoreErrors())
.Concat(ICantBelieveHowManyErrorsYouHave());
}
However, there is one significant difference between the two implementations: this one will immediately run all of the methods, despite only using the returned iterators one at a time. Your present code will not ask for the following errors until it has looped through everything in GetMoreErrors().
Normally, this isn’t a big deal, but it’s useful to know what’s going to happen when.
Answered by Jon Skeet
Solution #2
All of the error sources might be set up in the same way (method names copied from Jon Skeet’s solution).
private static IEnumerable<IEnumerable<ErrorInfo>> GetErrorSources(Card card)
{
yield return GetMoreErrors(card);
yield return GetOtherErrors();
yield return GetValidationErrors();
yield return AnyMoreErrors();
yield return ICantBelieveHowManyErrorsYouHave();
}
Then you can iterate on both of them at once.
private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
foreach (var errorSource in GetErrorSources(card))
foreach (var error in errorSource)
yield return error;
}
You might also use SelectMany to flatten the error sources.
private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
return GetErrorSources(card).SelectMany(e => e);
}
The methods in GetErrorSources will also take longer to execute.
Answered by Adam Boddington
Solution #3
I devised the following yield_ snippet:
Here’s the XML snippet:
<?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>
Answered by John Gietzen
Solution #4
I don’t see anything wrong with your function; in fact, it appears to be performing exactly what you want it to.
Consider the Yield as returning an element from the final Enumeration each time it is executed, so it returns one element each time it is invoked in the foreach loop. To filter the resultset, you can use conditional statements in your foreach loop. (By refusing to compromise on your exclusion criteria)
If you add more yields later in the procedure, the enumeration will continue to grow by one element, allowing you to accomplish things like…
public IEnumerable<string> ConcatLists(params IEnumerable<string>[] lists)
{
foreach (IEnumerable<string> list in lists)
{
foreach (string s in list)
{
yield return s;
}
}
}
Answered by Tim Jarvis
Solution #5
I’m amazed no one has suggested using a simple Extension method on IEnumerableIEnumerableT>> to retain the deferred execution of this function. Deferred execution appeals to me for a variety of reasons, one of which is that it has a modest memory footprint, even for really large 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;
}
}
}
}
And you may put it to use in your situation like this.
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
}
Similarly, instead of wrapping DoGetErrors in a wrapper function, you may just move UnWrap to the callsite.
Answered by Frank Bryce
Post is based on https://stackoverflow.com/questions/1270024/return-all-enumerables-with-yield-return-at-once-without-looping-through