2008-12-21 78 views
8

仍然試圖找到我會在真實情況下使用「yield」關鍵字的位置。增加收益關鍵字值?

我看到這個線程的主題

What is the yield keyword used for in C#?

但在接受的答案,他們有這樣的作爲如果有人圍繞整數()

public IEnumerable<int> Integers() 
{ 
yield return 1; 
yield return 2; 
yield return 4; 
yield return 8; 
yield return 16; 
yield return 16777216; 
} 

但爲什麼不迭代的例子只需使用

list<int> 

這裏改爲。似乎更簡單..

+0

的可能重複(http://stackoverflow.com/questions/17125/what-are-real-life-applications-of-yield) – nawfal 2014-07-08 12:42:11

+0

答案可以在這裏找到:http://stackoverflow.com/questions/14057788/why-use-the-yield-keyword-when - 我可以只使用一個普通的ienumerable – hdoghmen 2015-06-17 10:09:17

回答

23

如果你建立並返回一個列表(說它有1個萬臺),這是大內存塊,並且還創造它的工作。

有時調用者可能只想知道第一個元素是什麼。或者,他們可能想在寫入文件時將它們寫入文件,而不是在內存中構建整個列表,然後將其寫入文件。

這就是爲什麼使用收益率回報更有意義。它與構建整個列表並返回它看起來沒有什麼不同,但它非常不同,因爲在調用者查看第一個項目之前,不必在內存中創建整個列表。

當來電說:

foreach (int i in Integers()) 
{ 
    // do something with i 
} 

每次循環需要一個新的我,其運行的代碼在整數位()。該函數中的代碼在遇到yield return語句時將「暫停」。

+1

我有問題要理解產量。但你的答案很好!我認爲使用yield或多或少像使用DataReader和DataSets的區別。通過DataSets,我們獲得了所有的數據,然後我們就可以開展工作,DataReader可以在數據從源到達時處理數據。 :-) – 2009-06-24 00:42:05

0

你可能想通過各種集合迭代:

public IEnumerable<ICustomer> Customers() 
{ 
     foreach(ICustomer customer in m_maleCustomers) 
     { 
      yield return customer; 
     } 

     foreach(ICustomer customer in m_femaleCustomers) 
     { 
      yield return customer; 
     } 

     // or add some constraints... 
     foreach(ICustomer customer in m_customers) 
     { 
      if(customer.Age < 16) 
      { 
       yield return customer; 
      } 
     } 

     // Or....    
     if(Date.Today == 1) 
     { 
      yield return m_superCustomer; 
     } 

} 
+1

如果你有興趣(而不知道Linq),你可以寫在整個事情爲:返回m_maleCustomers.Concat(m_femaleCustomers).Concat(m_customers.Where(c => c.Age <16))。Concat(Enumerable.Repeat(m_superCustomer,1).Where(Date.Today == 1) ; – 2008-12-21 14:14:57

4

您可以使用yield構建任何迭代器。這可能是一個懶散的評估系列(例如,從文件或數據庫讀取行,例如,沒有一次讀取所有內容,這可能太多,無法保存在內存中),或者可能迭代現有數據,如List<T>

C# in Depth有一個免費的章節(6)全部關於迭代塊。

我也blogged最近關於使用yield智能蠻力算法。

對於懶惰文件閱讀器的一個示例:

static IEnumerable<string> ReadLines(string path) { 
     using (StreamReader reader = File.OpenText(path)) { 
      string line; 
      while ((line = reader.ReadLine()) != null) { 
       yield return line; 
      } 
     } 
    } 

這是完全 「懶」; 什麼也沒有被讀取,直到你開始枚舉,並且只有一行被保存在內存中。

請注意,LINQ到對象使擴展使用迭代器塊(yield)。例如,Where擴展基本上是:

static IEnumerable<T> Where<T>(this IEnumerable<T> data, Func<T, bool> predicate) { 
     foreach (T item in data) { 
      if (predicate(item)) yield return item; 
     } 
    } 

再次,完全懶 - 讓你鏈接在一起的多個操作不強制所有內容被加載到內存中。

+0

關於懶惰的暴力的好文章,你期望有一個singel對象的列表如何高貴,你會使用Single()來確保?這是一個好習慣嗎? – CloudyMarble 2013-05-21 07:53:24

9

Yield允許您構建生成數據的方法,而無需在返回前收集所有內容。把它看作一路上返回多個值。

這裏有幾個是說明這一點的方法

public IEnumerable<String> LinesFromFile(String fileName) 
{ 
    using (StreamReader reader = new StreamReader(fileName)) 
    { 
     String line; 
     while ((line = reader.ReadLine()) != null) 
      yield return line; 
    } 
} 

public IEnumerable<String> LinesWithEmails(IEnumerable<String> lines) 
{ 
    foreach (String line in lines) 
    { 
     if (line.Contains("@")) 
      yield return line; 
    } 
} 

無論是這兩種方法都將讀取該文件的全部內容到內存,但你可以使用它們像這樣:

foreach (String lineWithEmail in LinesWithEmails(LinesFromFile("test.txt"))) 
    Console.Out.WriteLine(lineWithEmail); 
3

yield允許您處理可能無限大的集合,因爲與基於列表的方法不同,整個集合不會一次裝入內存。例如,所有素數的IEnumerable <>可以通過用於找到素數的適當算法得到回退,而List方法總是有限的,因此是不完整的。在這個例子中,使用yield還允許處理下一個元素,直到它被需要。

1

對我來說真實的情況是,當我想處理需要一段時間才能更流暢地填充的集合時。

想象沿東西線(僞代碼):

public IEnumberable<VerboseUserInfo> GetAllUsers() 
{ 
    foreach(UserId in userLookupList) 
    { 
     VerboseUserInfo info = new VerboseUserInfo(); 

     info.Load(ActiveDirectory.GetLotsOfUserData(UserId)); 
     info.Load(WebSerice.GetSomeMoreInfo(UserId)); 

     yield return info; 
    } 
} 

而不必等待一分鐘時間,收集填充之前,我可以開始處理在它的項目。我將能夠立即開始,然後在發生時向用戶界面回報。

0

我同意大家在這裏所說的關於懶惰評估和內存使用情況的所有內容,並且想要添加另一種場景,我發現使用yield關鍵字的迭代器很有用。我遇到了一些情況,我必須對一些數據進行一系列可能昂貴的處理,這對於使用迭代器非常有用。而不是立即處理整個文件,或者我自己的滾動處理管道,我可以簡單地使用迭代器是這樣的:

IEnumerable<double> GetListFromFile(int idxItem) 
{ 
    // read data from file 
    return dataReadFromFile; 
} 

IEnumerable<double> ConvertUnits(IEnumerable<double> items) 
{ 
    foreach(double item in items) 
     yield return convertUnits(item); 
} 

IEnumerable<double> DoExpensiveProcessing(IEnumerable<double> items) 
{ 
    foreach(double item in items) 
     yield return expensiveProcessing(item); 
} 

IEnumerable<double> GetNextList() 
{ 
    return DoExpensiveProcessing(ConvertUnits(GetListFromFile(curIdx++))); 
} 

這裏的好處是,通過保持輸入和輸出到所有的功能IEnumerable<double>,我處理管道是完全可組合的,易於閱讀和懶惰評估,所以我只需要做我真正需要做的處理。這使我可以將幾乎所有的處理都放在GUI線程中,而不會影響響應性,所以我不必擔心任何線程問題。

1

您可能並不總是希望使用yield而不是返回一個列表,並且在您的示例中使用yield實際返回整數列表。取決於你想要一個可變列表還是一個不可變序列,你可以使用一個列表或一個迭代器(或其他一些可變/不可變的集合)。

但使用良率有好處。

  • Yield提供了一種構建惰性評估迭代器的簡單方法。(意味着當調用MoveNext()方法時,只執行獲得下一個元素的代碼,然後迭代器不再執行計算,直到再次調用該方法爲止)

  • 產量建立一個狀態機,通過不必編碼泛型生成器的狀態=>更簡潔/簡單的代碼,可以節省您的工作量。

  • 產量自動構建優化的和線程安全的迭代器,從而讓您瞭解如何構建它們的細節。

  • 產量遠比看起來乍看起來強得多,可用於建立簡單迭代器,檢查此視頻以查看Jeffrey Richter and his AsyncEnumerator以及如何使用yield,使得使用異步模式的編碼變得容易。

0

我想出了這個來解決.net的缺點,不得不手動深層複製List。

我用這個:

static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements) 
{ 
    foreach (SpotPlacement sp in spotPlacements) 
    { 
     yield return (SpotPlacement)sp.Clone(); 
    } 
} 

而在另一個地方:

public object Clone() 
{ 
    OrderItem newOrderItem = new OrderItem(); 
    ... 
    newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements)); 
    ... 
    return newOrderItem; 
} 

我試着拿出oneliner做這個工作,但它是不可能的,因爲產生內部匿名不工作方法塊。

編輯:

更妙的是,使用通用名單拷貝機:

class Utility<T> where T : ICloneable 
{ 
    static public IEnumerable<T> CloneList(List<T> tl) 
    { 
     foreach (T t in tl) 
     { 
      yield return (T)t.Clone(); 
     } 
    } 
} 
0

通過即時加工項目所用節省內存的yield的方法是很好的,但實際上它只是語法糖。它已經存在很長時間了。在任何具有函數或接口指針(甚至是C和彙編)的語言中,您都可以使用回調函數/接口獲得相同的效果。

這種花哨的東西:

static IEnumerable<string> GetItems() 
{ 
    yield return "apple"; 
    yield return "orange"; 
    yield return "pear"; 
} 

foreach(string item in GetItems()) 
{ 
    Console.WriteLine(item); 
} 

基本上等同於老式:?什麼是產量的實際生活中的應用]

interface ItemProcessor 
{ 
    void ProcessItem(string s); 
}; 

class MyItemProcessor : ItemProcessor 
{ 
    public void ProcessItem(string s) 
    { 
     Console.WriteLine(s); 
    } 
}; 

static void ProcessItems(ItemProcessor processor) 
{ 
    processor.ProcessItem("apple"); 
    processor.ProcessItem("orange"); 
    processor.ProcessItem("pear"); 
} 

ProcessItems(new MyItemProcessor());