26

Why can't we use both return and yield return in the same method?

For example, we can have GetIntegers1 and GetIntegers2 below, but not GetIntegers3.

public IEnumerable<int> GetIntegers1()
{
  return new[] { 4, 5, 6 };
}

public IEnumerable<int> GetIntegers2()
{
  yield return 1;
  yield return 2;
  yield return 3;
}

public IEnumerable<int> GetIntegers3()
{
  if ( someCondition )
  {
    return new[] {4, 5, 6}; // compiler error
  }
  else
  {
    yield return 1;
    yield return 2;
    yield return 3;
  }
}
Setyo N
  • 1,953
  • 2
  • 26
  • 28
  • 13
    wait a second, jon skeet will come now. – Juvanis Mar 09 '12 at 08:52
  • I'll add that if you really need it, you could create a GetIngegers4 that calls GetIntegers1 OR GetIntegers2 depending on a condition. – xanatos Mar 09 '12 at 09:03
  • This is probably obvious, but in such cases you could always unroll your collection and yield return the items: foreach(var item in new[]{4,5,6}) yield return item; – Foo42 Mar 09 '12 at 09:04

5 Answers5

22

return is eager. It returns the entire resultset at once. yield return builds an enumerator. Behind the scenes the C# compiler emits the necessary class for the enumerator when you use yield return. The compiler doesn't look for runtime conditions such as if ( someCondition ) when determining whether it should emit the code for an enumerable or have a method that returns a simple array. It detects that in your method you are using both which is not possible as he cannot emit the code for an enumerator and at the same time have the method return a normal array and all this for the same method.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • When i try to debug the code, i can see that it travels all the lines of code in the iteration. Then how come we can say that it builds the enumerator internally and return it only once... – Karan May 21 '12 at 10:25
12

No you can't do that - an iterator block (something with a yield) cannot use the regular (non-yield) return. Instead, you need to use 2 methods:

public IEnumerable<int> GetIntegers3()
{
  if ( someCondition )
  {
    return new[] {4, 5, 6}; // compiler error
  }
  else
  {
    return GetIntegers3Deferred();
  }
}
private IEnumerable<int> GetIntegers3Deferred()
{
    yield return 1;
    yield return 2;
    yield return 3;
}

or since in this specific case the code for both already exists in the other 2 methods:

public IEnumerable<int> GetIntegers3()
{
  return ( someCondition ) ? GetIntegers1() : GetIntegers2();
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
8

The compiler re-writes any methods with a yield statement (return or break). It currently cannot handle methods that may or may not yield.

I'd recommend having a read of chapter 6 of Jon Skeet's C# in Depth, of which chapter 6 is available for free - it covers iterator blocks quite nicely.

I see no reason why this would not be possible in future versions of the c# compiler however. Other .Net languages do support something similar in the form of a 'yield from' operator (See F# yield!). If such an operator existed in c# it would allow you to write your code in the form:

public IEnumerable<int> GetIntegers()
{
  if ( someCondition )
  {
    yield! return new[] {4, 5, 6};
  }
  else
  {
    yield return 1;
    yield return 2;
    yield return 3;
  }
}
Rich O'Kelly
  • 41,274
  • 9
  • 83
  • 114
3

Theoretically I think there is no reason why return and yield return can not be mixed: It would be an easy matter for the compiler to first syntactically transform any return (blabla()); sentence into:

var myEnumerable = blabla();
foreach (var m in myEnumerable) 
    yield return m; 
yield break; 

and then continue (to transform the whole method into ... whatever they transform it now; an inner anonymous IEnumerator class?!)

So why did they not choose to implement it, here are two guesses:

  • they might have decided it would be confusing to the users to have both return and yield return at once,

  • Returning the whole enumerable is faster and cheaper but also eager; building via yield return is a little more expensive (especially if called recursively, see Eric Lippert's warning for traversal in binary trees with yield return statements here: https://stackoverflow.com/a/3970171/671084 for example) but lazy. So a user usually would not want to mix these: If you don't need laziness (i.e. you know the whole sequence) don't suffer the efficiency penalty, just use a normal method. They might have wanted to force the user to think along these lines.

On the other hand, it does seem that there are situations where the user could benefit from some syntactic extensions; you might want to read this question and answers as an example (not the same question but one probably with a similar motive): Yield Return Many?

Community
  • 1
  • 1
Ali Ferhat
  • 2,511
  • 17
  • 24
  • This is actually an answer to "What could have been the reasons Microsoft decided not to support...", which would have been a better question in the first place. – R. Schreurs Apr 07 '16 at 15:24
1

I think the main reason why it doesn't work is because designing it in a way that is not overcomplicated but is performant at the same time would be hard, with relatively little benefit.

What exactly would your code do? Would it directly return the array, or would it iterate over it?

If it would directly return the array, then you would have to think up complicated rules under what conditions is return allowed, because return after yield return doesn't make sense. And you would probably need to generate complicated code to decide, whether the method will return custom iterator or the array.

If you wanted to iterate the collection, you probably want some better keyword. Something like yield foreach. That was actually considered, but was ultimately not implemented. I think I remember reading the main reason is that it's actually really hard to make it perform well, if you have several nested iterators.

Community
  • 1
  • 1
svick
  • 236,525
  • 50
  • 385
  • 514
  • 1
    It is hard to make it perform well with nested iterators, but that problem can be solved with some cleverness; they did so in C-Omega. However, if you do so then you start to run into correctness problems when those nested iterators contain exception handling. It is a lovely feature idea and I wish we could do it, but the pain-to-gain ratio is too high. – Eric Lippert Mar 13 '12 at 16:40