我知道yield关键字表示它出现的方法是迭代器。我只是想知道它是如何工作的List<T>.AddRange
。
让我们使用下面的例子:
static void Main()
{
foreach (int i in MyInts())
{
Console.Write(i);
}
}
public static IEnumerable<int> MyInts()
{
for (int i = 0; i < 255; i++)
{
yield return i;
}
}
因此,在每个yield之后的上述示例中,将在foreach
中的Main
循环中返回一个值,并将其打印到控制台。
如果我们将Main
更改为:
static void Main()
{
var myList = new List<int>();
myList.AddRange(MyInts());
}
这是怎么回事?是否为yield语句返回的每个int调用AddRange
,还是在添加整个范围之前以某种方式等待所有255个值?
答案 0 :(得分:4)
implementation of AddRange
将使用迭代器的IEnumerable
方法迭代.MoveNext()
输入,直到您的yield
方法生成了所有值。这可以看作here。
因此myList.AddRange(MyInts());
被调用一次,其实现强制MyInts
返回所有值,然后再继续。
AddRange
耗尽了迭代器的所有值,因为它是如何实现的,但是下面的假设方法只会计算迭代器的第一个值:
public void AddFirst<T>(IEnumerable<T> collection)
{
Insert(collection.First());
}
有趣的实验是,在Console.WriteLine(i);
方法中添加MyInts
行,以查看每个数字的生成时间。
答案 1 :(得分:0)
有趣的问题。
如果枚举是针对实现ICollection的类(例如另一个列表或数组),则行为是不同的,但是假设它没有,因为您的示例没有。 AddRange()只是使用枚举器将项目一次插入列表中。
using(IEnumerator<T> en = collection.GetEnumerator()) {
while(en.MoveNext()) {
Insert(index++, en.Current);
如果枚举器的类型是ICollection,则AddRange首先展开列表,然后进行块复制。
如果您想亲自查看代码: https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,51decd510e5bfe6e
答案 2 :(得分:0)
简答:当您致电AddRange
时,它会在内部迭代您IEnumerable
中的所有项目并添加到列表中。
如果你做了这样的事情:
var myList = new List<int>();
myList.AddRange(MyInts());
foreach (int i in myList)
{
Console.Write(i);
}
然后,您的值将从开始到结束迭代两次:
for
循环现在,我们假设您为此AddRange
创建了自己的扩展方法:
public static IEnumerable<T> AddRangeLazily<T>(this ICollection<T> col, IEnumerable<T> values)
{
foreach (T i in values)
{
yield return i; // first we yield
col.Add(i); // then we add
}
}
然后你可以像这样使用它:
foreach (int i in myList.AddRangeLazily(MyInts()))
{
Console.Write(i);
}
...它也会被迭代两次,两次都不会从开始到结束。它会懒惰地将每个值添加到列表/集合中,同时允许您在添加每个新项目后执行其他操作(例如将其打印到输出)。
如果你有某种逻辑可以在操作过程中阻止添加到列表中,这应该会有所帮助。
如果这个AddRangeLazily
是缺点:只有在像我的代码示例一样迭代AddRangeLazily
时,才会将值添加到集合中。如果你这样做:
var someList = new List<int>();
someList.AddRangeLazily(MyInts());
if (someList.Any())
// it wouldn't enter here...
......它根本不会增加价值。如果您想要这种行为,则应使用AddRange
。但是,强制迭代在AddRangeLazily
方法上会起作用:
var someList = new List<int>();
someList.AddRangeLazily(MyInts());
if (someList.AddRangeLazily(MyInts()).Count())
// it would enter here...thus adding all values to the someList
...但是,根据懒惰是您调用的方法,它不会迭代所有内容。例如:
var someList = new List<int>();
someList.AddRangeLazily(MyInts());
if (someList.AddRangeLazily(MyInts()).Any())
// it would enter here, plus adding only the first value to someList
一旦任何项存在,Any()
为真,那么Any()
只需要一次迭代即可返回true
,因此它只需要第一项迭代。
我实际上不记得要做这样的事情,只是为了和yield
一起玩。