2010-03-16 88 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切紙機死亡」的情況。