What is better when using an IEnumerable with one item: yield return or return []?
Asked Answered
I

4

11

This is one of those "you can do it many ways" questions. Consider the following code:

protected virtual IEnumerable<ScriptReference> GetScriptReferences()
{
    ScriptReference referece = new ScriptReference();
    referece.Assembly = "FeyenoordEnabled";
    referece.Name = "FeyenoordEnabled.PassTextBox.js";

    return new ScriptReference[] { referece }; 
}

protected virtual IEnumerable<ScriptReference> GetScriptReferences()
{
    ScriptReference referece = new ScriptReference();
    referece.Assembly = "FeyenoordEnabled";
    referece.Name = "FeyenoordEnabled.PassTextBox.js";

    yield return referece;
}

I only need to return one item. The first piece of codes returns an array with a single item and the second one yields the item. What is better and why?

Impossibly answered 21/11, 2011 at 21:21 Comment(3)
Surely it's not possible to be any more efficient than the first return?December
You could also return Enumerable.Repeat(reference, 1); though this boils down to a yield return under the covers...Gymnasiarch
I'll note one more use for yield: creating a generated, unending IEnumerable (say, a Fibonnaci sequence generator). You have to be careful not to try to loop through the whole IEnumerable if you are going to do that. But, anecdotally, I can tell you that I avoid yield return whenever it is sensible to do so for performance reasons. It seems that others are confirming this practice.Crary
B
7

Profile profile profile. Here is a A-B comparison using mono:

public static IEnumerable<int> UsingYield()
{
    yield return 42;
}
public static IEnumerable<int> ReturningArray()
{
    return new []{ 42 };
}

(Compiled with -optimize+ enabled)

The yield version instantiates a class that implements IEnumerable and the whole shebang:

Note I left out the 163 lines of CIL code implementing the enumerator block 'anonymous' type Program/'<UsingYield>c__Iterator0'. See it all here: https://gist.github.com/1384014

.method public static hidebysig 
       default class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> UsingYield ()  cil managed 
{
    .custom instance void class [mscorlib]System.Diagnostics.DebuggerHiddenAttribute::'.ctor'() =  (01 00 00 00 ) // ....

    // Method begins at RVA 0x20f4
// Code size 16 (0x10)
.maxstack 3
.locals init (
    class Program/'<UsingYield>c__Iterator0'    V_0)
IL_0000:  newobj instance void class Program/'<UsingYield>c__Iterator0'::'.ctor'()
IL_0005:  stloc.0 
IL_0006:  ldloc.0 
IL_0007:  dup 
IL_0008:  ldc.i4.s 0xfffffffe
IL_000a:  stfld int32 Program/'<UsingYield>c__Iterator0'::$PC
IL_000f:  ret 
} // end of method Program::UsingYield

The array version seems much simpler:

.method public static hidebysig 
       default class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> ReturningArray ()  cil managed 
{
    // Method begins at RVA 0x2110
// Code size 12 (0xc)
.maxstack 8
IL_0000:  ldc.i4.1 
IL_0001:  newarr [mscorlib]System.Int32
IL_0006:  dup 
IL_0007:  ldc.i4.0 
IL_0008:  ldc.i4.s 0x2a
IL_000a:  stelem.i4 
IL_000b:  ret 
} // end of method Program::ReturningArray

On the actual runtime performance, PROFILE PROFILE PROFILE!

Backblocks answered 21/11, 2011 at 21:27 Comment(4)
If yield is so expensive, when would it be 'right' to use so?Impossibly
@KeesC.Bakker: It is complex in terms of generated code. You'd use it for reasons mentioned elsewhere: (a) deferred execution (b) abstraction (you get state-machine functionality at the cost of no visible complexity to the programmer). Also, no-one says that a JIT engine would not inline the relevant methods and get close to the same result. Profile, profile, profile!Backblocks
OK mr. Prophet... 'From this day on I shall go forth and profile!'Impossibly
For those that don't want to profile, C# 7/.Net 4.7 there is a slight speed advantage to yield return.Irresolute
E
9

yield is a pretty expensive keyword. You are telling the compiler to do a lot. If performance isn't an issue, go with the more elegant code. But if performance is an issue, stick with the array.

I can say from past experience that getting rid of this type of yield usage has netted me some serious performance gains. But as always, profile and find the real bottlenecks.

Emplane answered 21/11, 2011 at 21:23 Comment(2)
If yield is so expensive, when would it be 'right' to use so?Impossibly
Linq is based on the use ofthe yield keyword. yield makes your function lazy, if you don't fetch the elements, nothing happens. The case you posted, returning direcly makes sense, no need to use yield.Mallis
B
7

Profile profile profile. Here is a A-B comparison using mono:

public static IEnumerable<int> UsingYield()
{
    yield return 42;
}
public static IEnumerable<int> ReturningArray()
{
    return new []{ 42 };
}

(Compiled with -optimize+ enabled)

The yield version instantiates a class that implements IEnumerable and the whole shebang:

Note I left out the 163 lines of CIL code implementing the enumerator block 'anonymous' type Program/'<UsingYield>c__Iterator0'. See it all here: https://gist.github.com/1384014

.method public static hidebysig 
       default class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> UsingYield ()  cil managed 
{
    .custom instance void class [mscorlib]System.Diagnostics.DebuggerHiddenAttribute::'.ctor'() =  (01 00 00 00 ) // ....

    // Method begins at RVA 0x20f4
// Code size 16 (0x10)
.maxstack 3
.locals init (
    class Program/'<UsingYield>c__Iterator0'    V_0)
IL_0000:  newobj instance void class Program/'<UsingYield>c__Iterator0'::'.ctor'()
IL_0005:  stloc.0 
IL_0006:  ldloc.0 
IL_0007:  dup 
IL_0008:  ldc.i4.s 0xfffffffe
IL_000a:  stfld int32 Program/'<UsingYield>c__Iterator0'::$PC
IL_000f:  ret 
} // end of method Program::UsingYield

The array version seems much simpler:

.method public static hidebysig 
       default class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> ReturningArray ()  cil managed 
{
    // Method begins at RVA 0x2110
// Code size 12 (0xc)
.maxstack 8
IL_0000:  ldc.i4.1 
IL_0001:  newarr [mscorlib]System.Int32
IL_0006:  dup 
IL_0007:  ldc.i4.0 
IL_0008:  ldc.i4.s 0x2a
IL_000a:  stelem.i4 
IL_000b:  ret 
} // end of method Program::ReturningArray

On the actual runtime performance, PROFILE PROFILE PROFILE!

Backblocks answered 21/11, 2011 at 21:27 Comment(4)
If yield is so expensive, when would it be 'right' to use so?Impossibly
@KeesC.Bakker: It is complex in terms of generated code. You'd use it for reasons mentioned elsewhere: (a) deferred execution (b) abstraction (you get state-machine functionality at the cost of no visible complexity to the programmer). Also, no-one says that a JIT engine would not inline the relevant methods and get close to the same result. Profile, profile, profile!Backblocks
OK mr. Prophet... 'From this day on I shall go forth and profile!'Impossibly
For those that don't want to profile, C# 7/.Net 4.7 there is a slight speed advantage to yield return.Irresolute
M
4

The first one returns directly when you call it with the array you created it.

The second one, since you are using yield, it wont even execute until you start fetching the elements (well in your case one element).

So it really depends what you want to do, but just be aware different behavior.

Mallis answered 21/11, 2011 at 21:24 Comment(0)
S
1

After profiling with Benchmark.NET , it is clear that using array for single value is faster than using yield return.

Tested using .NET Core 3.1.2 x64 Windows

Stelmach answered 22/3, 2020 at 13:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.