1
votes

I am writing validation logic, and I wanted the caller to only get the number of validation messages they really need (some cases, just the first validation message is necessary, other times, we want to now all of the problems with the given data)

Given this, I thought "Brilliant! I'll return an IEnumerable, and use the yield return each of the results. if FirstOrDefault() is used on the enumeration, only the first failed validation will be executed, where as the following will be skipped, unless we call ToList() on the validation result enumerable.

The issue I am seeing is if I want to break my validation logic into multiple methods, each returning an Enumerable, I have to enumerate over THAT set with another yield return there as well. (see simplified example below)

public IEnumerable<string> Validate(ClassToValidate obj)
{
  if(string.IsNullOrEmpty(obj.Name)
  {
     yield return "empty name";
  }
  foreach(var message in ValidateSubObject(obj.OtherObjectToValidate))
  {
    yield return message;
  }
}

private IEnumerable<string> ValidateSubObject(OtherClass objToValidate)
{
   yield return ...
}

Is there some other keyword I am missing, where I could "yield return set" from the other method that returns another IEnumerable of the same datatype? I.E. is there a simpler syntax than:

  foreach(var message in ValidateSubObject(obj.OtherObjectToValidate))
  {
    yield return message;
  }
2

2 Answers

4
votes

You cannot yield return multiple items. If you want to use iterator methods to concatenate sequences, you'll have to loop through them.

Of course, you could always drop the yield return completely and construct your IEnumerable<T> to be returned using other means (LINQ's Concat method immediately comes to mind).

public IEnumerable<string> Validate(ClassToValidate obj)
{
    var subObjectMessages = ValidateSubObject(obj.OtherObjectToValidate);

    if (string.IsNullOrEmpty(obj.Name))
    {
        return new[] { "empty name" }.Concat(subObjectMessages);
    }

    return subObjectMessages;
}
2
votes

Once you've introduced yield in a function, you have to stay with it. A common approach these days is to use LINQ, which is often more flexible.

public IEnumerable<string> Validate(ClassToValidate obj)
{
  return (String.IsNullOrEmpty(obj.Name) ? new [] { "empty name" } : Enumerable.Empty<string>())
      .Concat(ValidateSubObject(obj.OtherObjectToValidate));
}