Reputation: 46173
These two methods appear to behave the same to me
public IEnumerable<string> GetNothing()
{
return Enumerable.Empty<string>();
}
public IEnumerable<string> GetLessThanNothing()
{
yield break;
}
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.
Are there any reasons to use one over the other? Is one easier to read than the other? Is there a behavior difference that would matter to a caller?
Upvotes: 59
Views: 23556
Reputation: 1744
If you do not intend to always return an empty enumerable, then I would like to add that yield
is lazy, i.e. it will be evaluated every time the enumerator is queried.
The following test demonstrates the laziness:
public void TestYieldEnumerator()
{
var returnItems = false;
var e = getEmptyEnumerator();
var y = getEmptyYield();
Assert.AreEqual(e.Count(), 0);
Assert.AreEqual(y.Count(), 0);
returnItems = true;
Assert.AreEqual(e.Count(), 0);
Assert.AreEqual(y.Count(), 1); //the second query returns items
IEnumerable<string> getEmptyEnumerator() => !returnItems
? Enumerable.Empty<string>()
: new List<string>() { "item" };
IEnumerable<string> getEmptyYield()
{
if (returnItems) yield return "item";
}
}
Upvotes: 0
Reputation: 41648
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.
Upvotes: 8
Reputation: 21062
Funny thing, I read this post this morning, and few hours later I was hit by this example -- found the difference when you have more code:
public static IEnumerable<T> CoalesceEmpty<T>(IEnumerable<T> coll)
{
if (coll == null)
return Enumerable.Empty<T>();
else
return coll;
}
You cannot change the first return to yield break, because you would have to change the second return as well (to longer version).
Upvotes: 4
Reputation: 4016
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 :)
Upvotes: 23
Reputation: 754655
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.
Upvotes: 49
Reputation: 13511
It would seem that yield break
would instantiate at least one less object than what return Enumerable.Empty<string>()
would do. Additionally, there may be some checks that you would be short-circuiting with yield break
. And if nothing else, it's one less function wrapper that your stack goes through which would be detectable although not noticeable.
However, I agree with the other answer posted that .Empty is the "preferred" way of doing this.
Upvotes: 0