6

Let's say I have the following method. In some came

    public IEnumerable<ValidationResult> Validate(UserLoginCommand command)
    {
        User user = userRepository.Get(u => u.Email == command.UserEmail);
        if(user != null)
        {
            if(!user.Activated)
            {
                return new IEnumerable<ValidationResult>() {new ValidationResult("NotActived", Resources.UserNotActivated)};
            }

            if(user.IsPasswordIncorrent)
            {

                yield return new ValidationResult("IncorrectPassword", Resources.IncorrentPassword);

            }

        }
    }

The actual situation is actually a bit more complex but I've left a lot out for illustration purposes.

The point is in some cases, I want to iterator to continue collecting multiple errors...but in other cases there is a fatal error and I only want to return a single error but it will not let me:

Iterator cannot contain return statement 

What should I do?

Mike Christensen
  • 88,082
  • 50
  • 208
  • 326
parliament
  • 21,544
  • 38
  • 148
  • 238
  • 2
    I think the error message is pretty explicit. You can't mix `yield` and `return`, as the function execution is deferred until the iterator is called upon. I'd suggest getting rid of the `yield` and just building your own enumeration. I'm hoping Jon Skeet or Eric Lippert will chime in here on exactly why the compiler can't handle this case though. – Mike Christensen Dec 19 '12 at 22:02

3 Answers3

10

If you just want to return a collection of size one, you can do this:

if(!user.Activated)
{
  yield return new ValidationResult("NotActived", Resources.UserNotActivated);
  yield break;
}
Paul Phillips
  • 6,093
  • 24
  • 34
4

As indicated by the error message, you can't mix yield return statements and return statements into a single method.

You have two general approaches:

  1. The method should be eagerly evaluated; you should use all return statements, find a way to convert all yield statements into returns.
  2. The method should use deferred execution, find a way to turn all return statements into yield statements.

In your case, it's probably #2, but in other situations either may be appropriate.

Now, how to turn a yield into a return:

wrap the single element into a collection of some sort and return that:

return new[]{ someItemToReturn };

or

return Enumerable.Repeat<T>(someItemToReturn, 1);

Now, how to turn a return into a yield:

foreach(var item in collectionYouWereReturning)
    yield return item;

You can use yield break; to indicate that the sequence has ended even if the method has not reached it's natural end. Note that yield break; should be pretty rarely used. Using it a lot would be code smell (but this does seem like a case where it would be appropriate).

Now, ideally, we'd have a yield foreach keyword of some sort so that you could yield a collection inside of an iterator block, but as of yet no such keyword has been added to the language.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • 1
    Thank you for the response, I learned a few useful things that will be useful in the future but in this case I preferred to keep the same pattern I used everywhere (yield return) but just return a single element in exceptional cases. Paul Phillips answer was simple and worked. Upvote for sure. – parliament Dec 19 '12 at 22:20
3

Shouldn't that return statement actually be a yield?

yield return ValidationResult("NotActived", Resources.UserNotActivated);

If you really need to return a collection, you can yield return a collection, too (like you have it), it's just not necessary since you have only one.

Also, in the case that you want to stop enumerating explicitly, you can use yield break;

Michael Haren
  • 105,752
  • 40
  • 168
  • 205