仍然试图找到在实际情况下我会在哪里使用“yield”关键字。
我在主题上看到了这个主题
What is the yield keyword used for in C#?
但是在接受的答案中,他们将此作为一个例子,其中某人正在迭代Integers()
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 :(得分:23)
如果你构建并返回一个List(比如它有100万个元素),那就是一大块内存,也是创建它的工作。
有时调用者可能只想知道第一个元素是什么。或者他们可能希望在获取文件时将它们写入文件,而不是在内存中构建整个列表,然后将其写入文件。
这就是为什么使用收益率回报更有意义。它与构建整个列表并返回它看起来并没有什么不同,但它是非常不同的,因为在调用者可以查看其中的第一个项目之前,不必在内存中创建整个列表。
来电者说:
foreach (int i in Integers())
{
// do something with i
}
每次循环需要一个新的i时,它会在Integers()中运行更多的代码。该函数中的代码在遇到yield return
语句时会“暂停”。
答案 1 :(得分: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);
答案 2 :(得分:4)
您可以使用yield
构建任何迭代器。这可能是一个懒惰的评估系列(例如,从文件或数据库读取行,而不是一次性读取所有内容,这可能在内存中保留太多),或者可能在现有数据上进行迭代,例如{{1} }。
C# in Depth有一个关于迭代器块的免费章节(6)。
我最近也blogged使用List<T>
进行智能强力算法。
有关惰性文件阅读器的示例:
yield
这完全是“懒惰”;在您开始枚举之前,没有被读取,并且只有一行被保存在内存中。
请注意,LINQ到对象使扩展使用迭代器块( static IEnumerable<string> ReadLines(string path) {
using (StreamReader reader = File.OpenText(path)) {
string line;
while ((line = reader.ReadLine()) != null) {
yield return line;
}
}
}
)。例如,yield
扩展名基本上是:
Where
再次,完全懒惰 - 允许您将多个操作链接在一起,而不必强制将所有内容加载到内存中。
答案 3 :(得分:2)
yield允许您处理可能无限大小的集合,因为整个集合永远不会一次性加载到内存中,这与基于List的方法不同。例如,IEnumerable&lt;&gt;所有素数都可以通过适当的算法来找回素数,而列表方法的大小总是有限的,因此不完整。在此示例中,使用yield还允许处理下一个元素,直到需要它为止。
答案 4 :(得分:1)
对我来说真实的情况是,当我想处理一个需要一段时间才能更顺畅地填充的集合时。
想象一下(PSuedo代码):
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;
}
}
在我开始处理其中的项目之前,不必等待集合填充。我将能够立即启动,然后在发生时向用户界面报告。
答案 5 :(得分:1)
您可能并不总是希望使用yield而不是返回列表,并且在您的示例中,您使用yield来实际返回整数列表。根据您是否需要可变列表或不可变序列,您可以使用列表或迭代器(或其他一些muttable / immutable集合)。
但使用产量有好处。
Yield提供了一种构建惰性求值迭代器的简便方法。 (意味着只有在调用MoveNext()方法时才会执行顺序获取下一个元素的代码,然后迭代器返回不再执行计算,直到再次调用该方法)
Yield构建了一个状态机,它不需要对通用生成器的状态进行编码,从而为您节省了大量的工作量。更简洁/简单的代码。
Yield会自动构建优化的和线程安全的迭代器,为您提供有关如何构建它们的详细信息。
产量比初看起来要强大得多,并且不仅可以用于构建简单的迭代器,还可以查看此视频以查看Jeffrey Richter and his AsyncEnumerator以及如何使用yield进行编码异步模式很容易。
答案 6 :(得分: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;
}
}
答案 7 :(得分: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线程中,而不会影响响应性,所以我不必担心任何线程问题。
答案 8 :(得分: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,但由于无法在匿名方法块中工作,因此无法实现。
编辑:
更好的是,使用通用List cloner:
class Utility<T> where T : ICloneable
{
static public IEnumerable<T> CloneList(List<T> tl)
{
foreach (T t in tl)
{
yield return (T)t.Clone();
}
}
}
答案 9 :(得分: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());