Mike Two
Mike Two

Reputation: 46173

Is yield break equivalent to returning Enumerable<T>.Empty from a method returning IEnumerable<T>

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

Answers (6)

mike
mike

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

Edward Brey
Edward Brey

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

greenoldman
greenoldman

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

Fede
Fede

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

JaredPar
JaredPar

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

Jaxidian
Jaxidian

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

Related Questions