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.
pu
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;
}
}
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.
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?
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();
}
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.