列出<t> .AddRange和yield语句

时间:2017-07-26 02:22:50

标签: c# .net list foreach

我知道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个值?

3 个答案:

答案 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一起玩。

Fiddle here!!!