当使用只有一个项的IEnumerable时,yield return和return []哪个更好?

10

这是一个典型的“有多种实现方式”的问题。考虑下面的代码:

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;
}

我只需要返回一个项目。第一段代码返回一个只有一个项目的数组,而第二个则返回该项。哪一个更好,为什么?


第一个 return 已经非常高效了,不可能再更高效了吧? - James
你也可以使用 return Enumerable.Repeat(reference, 1);,但这实际上是在内部使用了 yield return... - James Michael Hare
我再提一种使用 yield 的方法:创建一个生成的、无限的 IEnumerable(比如,一个斐波那契数列生成器)。如果你要这样做,就要小心不要尝试循环遍历整个 IEnumerable。但是,从经验上讲,我可以告诉你,出于性能原因,我在合理的情况下尽量避免使用 yield return。似乎其他人也证实了这种做法。 - Andrew
4个回答

9

yield是一个非常消耗性能的关键字。你在告诉编译器做很多事情。如果性能不是问题,就选择更优雅的代码。但如果性能是一个问题,那就坚持使用数组。

从过去的经验来看,摆脱这种类型的yield用法给我带来了一些严重的性能提升。但像往常一样,要进行分析并找到真正的瓶颈。


如果“yield”如此昂贵,那么什么时候使用它才是“正确”的呢? - Kees C. Bakker
Linq基于使用yield关键字。yield使您的函数变得惰性,如果您不获取元素,则什么也不会发生。 您发布的情况下,直接返回是有意义的,无需使用yield。 - MBen

6

个人资料,个人资料,个人资料。这里是使用单声道进行A-B比较的内容:

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

(启用 -optimize+ 编译)
yield 版本实例化了一个实现 IEnumerable 接口的类,还有整个过程:
注意:我省略了 163 行 CIL 代码,这些代码实现了枚举器块的“匿名”类型 Program/'<UsingYield>c__Iterator0'。在此处查看全部内容: 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

数组版本似乎更简单:
.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

在实际的运行性能方面,要使用PROFILE PROFILE PROFILE!


如果“yield”如此昂贵,那么什么时候使用它才是“正确”的呢? - Kees C. Bakker
3
@KeesC.Bakker说:从生成的代码来看,它是复杂的。你会出于其他原因使用它:(a)延迟执行(b)抽象化(你可以获得状态机功能,而程序员看不到复杂性)。此外,没有人说JIT引擎不会内联相关方法并接近相同的结果。请进行性能分析! - sehe
好的,先生。预言家...“从今天起,我将前往分析!” - Kees C. Bakker
1
对于那些不想进行性能分析的人,使用C# 7/.Net 4.7中的yield return会稍微提高一点速度。 - NetMage

4

第一个函数在使用创建好的数组调用时会直接返回。

第二个函数使用了yield,只有在获取元素时(在您的情况下为一个元素)才会执行。

因此,实际行为取决于你想做什么,但请注意它们的不同行为。


1

使用Benchmark.NET 进行性能测试后,明显使用数组来存储单个值比使用yield return更快。

在Windows下使用 .NET Core 3.1.2 x64进行了测试。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接