Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return all enumerables with yield return at once; without looping through

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?

like image 735
John Oxley Avatar asked Aug 13 '09 04:08

John Oxley


People also ask

Is yield return thread safe?

As written it is thread safe but if you comment out the lock(_sync) in AllValues you should be able to verify that it is not thread safe by running it a few times. If you get an InvalidOperationException it proves that it is NOT thread safe.

Does yield break return null?

1. "yield break" breaks the Coroutine (it's similar as "return"). "yield return null" means that Unity will wait the next frame to finish the current scope. "yield return new" is similar to "yield return null" but this is used to call another coroutine.

How do you use yield return?

To use "yield return", you just need to create a method with a return type that is an IEnumerable (arrays and collections in. Net implements IEnumerable interface) with a loop and use "yield return" to return a value to set in the loop body.

What is the return type of IEnumerable?

IEnumerable is the return type from an iterator.


2 Answers

It's definitely not a stupid question, and it's something that F# supports with yield! for a whole collection vs yield 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 use Enumerable.Concat to make your code simpler:

private static IEnumerable<ErrorInfo> GetErrors(Card card) {     return GetMoreErrors(card).Concat(GetOtherErrors())                               .Concat(GetValidationErrors())                               .Concat(AnyMoreErrors())                               .Concat(ICantBelieveHowManyErrorsYouHave()); } 

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.

like image 130
Jon Skeet Avatar answered Sep 16 '22 17:09

Jon Skeet


You could set up all the error sources like this (method names borrowed from Jon Skeet's answer).

private static IEnumerable<IEnumerable<ErrorInfo>> GetErrorSources(Card card) {     yield return GetMoreErrors(card);     yield return GetOtherErrors();     yield return GetValidationErrors();     yield return AnyMoreErrors();     yield return ICantBelieveHowManyErrorsYouHave(); } 

You can then iterate over them at the same time.

private static IEnumerable<ErrorInfo> GetErrors(Card card) {     foreach (var errorSource in GetErrorSources(card))         foreach (var error in errorSource)             yield return error; } 

Alternatively you could flatten the error sources with SelectMany.

private static IEnumerable<ErrorInfo> GetErrors(Card card) {     return GetErrorSources(card).SelectMany(e => e); } 

The execution of the methods in GetErrorSources will be delayed too.

like image 25
Adam Boddington Avatar answered Sep 18 '22 17:09

Adam Boddington