当我可以只使用普通的 IEnumable 时,为什么要使用屈服关键字?

根据这个代码:

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关键字是干什么的了。它告诉编译器构建某种特定的东西(迭代器)。但为什么要用呢?除了代码少一点,它还能为我做什么?

12387 次浏览

使用 yield创建集合 懒惰。

假设你只需要前五样东西。按照你的方法,我必须循环通过 整个名单得到前五个项目。使用 yield,我只循环访问前五个项目。

对于“ list”代码,必须先处理完整的列表,然后才能将其传递给下一步。“屈服”版本会立即将处理过的项目传递到下一步。如果“下一步”包含一个。采取(10)”,然后“产量”版本将只处理前10个项目,而忘记其余的。“列表”代码将处理所有内容。

这意味着,当您需要进行大量处理和/或需要处理一长串项目时,您可以看到最大的差异。

迭代器块的好处是它们工作起来比较懒惰,所以您可以编写一个过滤方法,如下所示:

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 博客系列给出了一个示例实现 LINQtoObjects,它使得 沉重使用迭代器块。LINQ 从根本上来说就是懒惰的——把东西放在一个列表里根本就不是那么回事。

object jamesItem = null;
foreach(var item in FilteredList())
{
if (item.Name == "James")
{
jamesItem = item;
break;
}
}
return jamesItem;

当使用上面的代码循环 FilteredList ()并假设。Name = = “ James”将满足列表中的第2项,使用 yield的方法将生成两次。这是一种懒惰的行为。

而使用 list 的方法将向列表中添加所有 n 个对象,并将完整的列表传递给调用方法。

这正是可以突出显示 IEnumable 和 IList 之间差异的用例。

可以使用 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

... 等到控制台,直到取消。

屈服返回语句允许您一次只返回一个项目。您将收集列表中的所有项,然后再次返回该列表,这是一个内存开销。

我所看到的使用 yield的最好的现实例子是计算一个斐波那契数列。

考虑以下代码:

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 扩展并仅查询您需要的内容。

为什么要使用[屈服值] ? 除了它的代码比较少之外,它还能为我做些什么?

有时有用,有时没用。如果必须检查并返回整个数据集,那么使用屈服就没有任何好处,因为它所做的只是引入开销。

当只返回一个部分集合时,才是屈服真正发光的时候。我认为最好的例子是排序。假设您有一个对象列表,其中包含今年的日期和金额,并且您希望看到今年的第一批(5)记录。

为了实现这一点,列表必须按日期升序排序,然后采用前5个。如果这样做没有产量,完整的列表将不得不排序,直到确保最后两个日期是有序的。

但是,对于屈服值,一旦建立了前5个项目,排序停止并且结果可用。这可以节省大量的时间。