Is yield break equivalent to returning Enumerable<T>.Empty from a method returning IEnumerable<T>
Solution 1:
If you intend to always return an empty enumerable then using the Enumerable.Empty<string>()
syntax is more declarative IMHO.
The performance difference here is almost certainly not significant. I would focus on readability over performance here until a profiler showed you it was a problem.
Solution 2:
IEnumerable<T>
methods with yield break
or yield return
in their bodies gets transformed to state machines. In this kind of methods you can't mix yield returns with traditional returns. What I mean is that if you yield something in some part of the method, you can't return a ICollection in another.
In the other hand, suppose you're implementing a method with return type IEnumerable<T>
by adding items to a collection, and then returning a readonly copy of the collection. If by some reason you want to just return an empty collection you can't do a yield break
. All you can do is just return Enumerable.Empty<T>()
.
If you've profiled both ways, and there's no significant change, then you can just forget about it :)
Solution 3:
I've profiled each in test scenarios and I don't see a meaningful difference in speed, but the yield break version is slightly faster.
I'm going to guess that your profiling tests did not include program startup speed. The yield
construct works by generating a class for you. This extra code is great when it provides logic you need, but if not, it just adds to disk I/O, working set size, and JIT time.
If you open a program containing your test methods in ILSpy and turn off enumerator decompilation, you'll find a class named <GetLessThanNothing>d__0
with a dozen or so members. Its MoveNext
method looks like this:
bool IEnumerator.MoveNext()
{
int num = this.<>1__state;
if (num == 0)
{
this.<>1__state = -1;
}
return false;
}
EmptyEnumerable
works by lazily creating a static empty array. Perhaps checking whether the array needs to be created is the reason EmptyEnumerable
is slower than yield break
in isolated benchmarking, but it would likely take a whole lot of iterations to overcome the startup penalty, and either way would be unlikely to be noticeable overall, even in a "death by a thousand perf papercuts" scenario.