2010-03-16 82 views
32

这两种方法出现同样的行为给我是产量突破等同于从方法返回可枚举<T> .Empty返回的IEnumerable <T>

public IEnumerable<string> GetNothing() 
{ 
    return Enumerable.Empty<string>(); 
} 

public IEnumerable<string> GetLessThanNothing() 
{ 
    yield break; 
} 

我在测试场景异形每我没有看到速度有意义的差异,但yield break版本稍快。

是否有任何理由使用其中一种?一个比另一个更容易阅读吗?是否有一个行为差异对呼叫者有影响?

回答

30

如果你打算总是返回一个空的枚举,然后使用Enumerable.Empty<string>()语法是更具说明性的恕我直言。

这里的性能差异几乎肯定不显着。在这里,我将重点关注性能的可读性,直到分析人员向您展示这是一个问题。

+2

这里和其他任何地方(除非一个剖析器另有说明) – 2010-03-16 19:03:36

1

看起来yield break会实例化至少比return Enumerable.Empty<string>()会做的少一个对象。另外,可能会有一些检查结果与yield break短路。如果没有其他的东西,它是一个少量的函数包装,你的堆栈可以检测到,虽然不明显。

但是,我同意发布的其他答案.Empty是执行此操作的“首选”方式。

+8

你为什么这么想? 'Enumerable.Empty '每次都会返回相同的东西 - 事实上,我相信它确实如此。在第一次通话后,根本没有必要创造任何东西。我非常怀疑C#编译器会发现它可以为'yield break'情况做到这一点。 – 2010-03-16 19:17:03

+1

@Jon Skeet - 你说得对。 yield break版本确实实例化每次产生的“yield”类。 'Enumerable.Empty'足够聪明以便缓存 – 2010-03-16 19:21:34

+0

难道它不得不至少返回一个克隆拷贝(即一个新的实例化)吗? – Jaxidian 2010-03-16 19:27:09

9

IEnumerable<T>方法与yield breakyield return在他们的身体被转化为状态机。在这种方法中,您不能将收益回报与传统回报结合起来。我的意思是,如果你在方法的某个部分产生某种东西,你不能在另一部分中返回ICollection。

另一方面,假设您正在实现返回类型为IEnumerable<T>的方法,方法是将项添加到集合中,然后返回该集合的只读副本。如果由于某种原因,您想要返回一个空集合,则不能执行yield break。你所能做的只是返回Enumerable.Empty<T>()

如果你异型两种方式,而且也没有显著的变化,那么你可以忘掉它:)

+0

+1用于澄清通用可枚举/收益方法与收集构建方法,即每个方法*强制*使用其中一个或另一个的理由。 – downwitch 2014-04-15 23:45:52

+0

我说IEnumerable yield的行为类似于“可以重复使用的Python生成器”。 IEnumerable的优点是不必一次MemLoad整个列表或结果,所以使用yield通常比创建整个列表或数组并返回它更有效 – Felype 2017-10-20 15:03:31

5

有趣的事情,我今天早上看到这篇文章,而几个小时后,我被这个打击例如 - 发现差异,当你有更多的代码:

public static IEnumerable<T> CoalesceEmpty<T>(IEnumerable<T> coll) 
{ 
    if (coll == null) 
     return Enumerable.Empty<T>(); 
    else 
     return coll; 
} 

你不能改变第一回得休息一下,因为你将不得不改变第二回流以及(更长的版本)。

5

我已经在测试场景中对每个测试场景进行了剖析,并且我没有看到速度上有意义的差异,但是yield yield版本稍快。

我猜测你的分析测试不包括程序启动速度。 yield构造通过为您生成一个类来工作。当它提供你需要的逻辑时,这个额外的代码是很棒的,但如果不是,它只是增加了磁盘I/O,工作集大小和JIT时间。

如果您在ILSpy中打开一个包含测试方法的程序并关闭枚举器反编译功能,您会发现一个名为<GetLessThanNothing>d__0的类与十几个成员。通过懒洋洋地创建一个静态的空数组

bool IEnumerator.MoveNext() 
{ 
    int num = this.<>1__state; 
    if (num == 0) 
    { 
     this.<>1__state = -1; 
    } 
    return false; 
} 

EmptyEnumerable作品:它MoveNext方法看起来是这样的。也许检查是否需要创建数组是因为EmptyEnumerable在单独基准测试中比yield break要慢,但它可能需要花费大量迭代来克服启动惩罚,并且无论哪种方式都不太可能在整体上显而易见,即使在“一千个perf切纸机死亡”的情况。