I have the following function to get validation errors for a card. My question relates to dealing with GetErrors. Both methods have the same return type 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
}
Is it possible to return all the errors in GetMoreErrors
without having to enumerate through them?
Thinking about it this is probably a stupid question, but I want to make sure I'm not going wrong.
Yes it is possible to return all errors at once. Just return a
List<T>
orReadOnlyCollection<T>
.By returning an
IEnumerable<T>
you're returning a sequence of something. On the surface that may seem identical to returning the collection, but there are a number of difference, you should keep in mind.Collections
Sequences
IEnumerable<T>
allows for lazy evaluation, returningList<T>
does not).I came up with a quick
yield_
snippet:Here's the snippet XML:
It's definitely not a stupid question, and it's something that F# supports with
yield!
for a whole collection vsyield
for a single item. (That can be very useful in terms of tail recursion...)Unfortunately it's not supported in C#.
However, if you have several methods each returning an
IEnumerable<ErrorInfo>
, you can useEnumerable.Concat
to make your code simpler:There's one very important difference between the two implementations though: this one will call all of the methods immediately, even though it will only use the returned iterators one at a time. Your existing code will wait until it's looped through everything in
GetMoreErrors()
before it even asks about the next errors.Usually this isn't important, but it's worth understanding what's going to happen when.
You could set up all the error sources like this (method names borrowed from Jon Skeet's answer).
You can then iterate over them at the same time.
Alternatively you could flatten the error sources with
SelectMany
.The execution of the methods in
GetErrorSources
will be delayed too.I'm surprised no one has thought to recommend a simple Extension method on
IEnumerable<IEnumerable<T>>
to make this code keep its deferred execution. I'm a fan of deferred execution for many reasons, one of them is that the memory footprint is small even for huge-mongous enumerables.And you could use it in your case like this
Similarly, you can do away with the wrapper function around
DoGetErrors
and just moveUnWrap
to the callsite.I don't see anything wrong with your function, I'd say that it is doing what you want.
Think of the Yield as returning an element in the final Enumeration each time that it is invoked, so when you have it in the foreach loop like that, each time it is invoked it returns 1 element. You have the ability to put conditional statements in your foreach to filter the resultset. (simply by not yielding on your exclusion criteria)
If you add subsequent yields later in the method, it will continue to add 1 element to the enumeration, making it possible to do things like...