鉴于此代码:
IEnumerable<object> FilteredList()
{
foreach( object item in FullList )
{
if( IsItemInPartialList( item ) )
yield return item;
}
}
为什么我不应该这样编码?:
IEnumerable<object> FilteredList()
{
var list = new List<object>();
foreach( object item in FullList )
{
if( IsItemInPartialList( item ) )
list.Add(item);
}
return list;
}
我理解yield
关键字的作用。它告诉编译器构建某种东西(迭代器)。但为什么要用呢?除了代码略少,它对我有什么用呢?
答案 0 :(得分:236)
使用yield
使集合 lazy。
假设你只需要前五项。按照你的方式,我必须遍历整个列表以获得前五项。使用yield
,我只会遍历前五项。
答案 1 :(得分:127)
迭代器块的好处是它们可以懒惰地工作。所以你可以编写一个这样的过滤方法:
public static IEnumerable<T> Where<T>(this IEnumerable<T> source,
Func<T, bool> predicate)
{
foreach (var item in source)
{
if (predicate(item))
{
yield return item;
}
}
}
这样您就可以根据需要过滤流,而不是一次缓冲多个项目。例如,如果您只需要返回序列中的第一个值,为什么要将所有内容复制到新列表中?
作为另一个示例,您可以使用迭代器块轻松创建无限流。例如,这是一系列随机数:
public static IEnumerable<int> RandomSequence(int minInclusive, int maxExclusive)
{
Random rng = new Random();
while (true)
{
yield return rng.Next(minInclusive, maxExclusive);
}
}
如何在列表中存储无限序列?
我的Edulinq blog series提供了LINQ to Objects的示例实现,它使重使用迭代器块。 LINQ从根本上说是懒惰的 - 并且把事情放在列表中根本就不起作用。
答案 2 :(得分:42)
使用“list”代码,您必须先处理完整列表,然后才能将其传递给下一步。 “yield”版本将处理后的项目立即传递到下一步。如果“下一步”包含“.Take(10)”,则“yield”版本将仅处理前10个项目而忘记其余项目。 “list”代码将处理所有内容。
这意味着当您需要进行大量处理和/或需要处理很长的项目列表时,您会看到最大的差异。
答案 3 :(得分:23)
您可以使用yield
返回列表中不包含的项目。这是一个小样本,可以无限循环遍历列表,直到被取消。
public IEnumerable<int> GetNextNumber()
{
while (true)
{
for (int i = 0; i < 10; i++)
{
yield return i;
}
}
}
public bool Canceled { get; set; }
public void StartCounting()
{
foreach (var number in GetNextNumber())
{
if (this.Canceled) break;
Console.WriteLine(number);
}
}
这写
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
...等。到控制台直到取消。
答案 4 :(得分:10)
object jamesItem = null;
foreach(var item in FilteredList())
{
if (item.Name == "James")
{
jamesItem = item;
break;
}
}
return jamesItem;
当上面的代码用于遍历FilteredList()并假设item.Name ==“James”将在列表中的第二项上得到满足时,使用yield
的方法将产生两次。这是一种懒惰的行为。
使用list的方法将所有n个对象添加到列表中并将完整列表传递给调用方法。
这正是一个用例,可以突出显示IEnumerable和IList之间的区别。
答案 5 :(得分:7)
我见过使用yield
的最好的现实世界的例子是计算Fibonacci序列。
请考虑以下代码:
class Program
{
static void Main(string[] args)
{
Console.WriteLine(string.Join(", ", Fibonacci().Take(10)));
Console.WriteLine(string.Join(", ", Fibonacci().Skip(15).Take(1)));
Console.WriteLine(string.Join(", ", Fibonacci().Skip(10).Take(5)));
Console.WriteLine(string.Join(", ", Fibonacci().Skip(100).Take(1)));
Console.ReadKey();
}
private static IEnumerable<long> Fibonacci()
{
long a = 0;
long b = 1;
while (true)
{
long temp = a;
a = b;
yield return a;
b = temp + b;
}
}
}
这将返回:
1, 1, 2, 3, 5, 8, 13, 21, 34, 55
987
89, 144, 233, 377, 610
1298777728820984005
这很好,因为它允许您快速轻松地计算出无限系列,使您能够使用Linq扩展并仅查询您需要的内容。
答案 6 :(得分:1)
为什么要使用[yield]?除了代码略少,它对我有什么影响?
有时它很有用,有时候不是。如果必须检查并返回整个数据集,那么使用yield不会有任何好处,因为它所做的只是引入了开销。
当yield真正发光时,只返回一个部分集合。我认为最好的例子是排序。假设您有一个包含今年日期和美元金额的对象列表,您希望看到年份的第一(5)条记录。
为了实现这一点,列表必须按日期升序排序,然后取得前5个。如果这样做没有收益,那么整个列表必须进行排序,直到确保最后两个日期是有序的。
但是,对于产量,一旦建立了前5个项目,排序就会停止并且结果可用。这可以节省大量时间。
答案 7 :(得分:0)
yield return语句允许您一次只返回一个项目。您正在收集列表中的所有项目,并再次返回该列表,这是一个内存开销。