简体   繁体   中英

Does Take(x) in Linq stops enumerating when taking x objects?

For example, if I have this code:

    public static void Main(string[] args)
    {
        List<int> list = new List<int>() { 2, 3, 2, 9, 10, 2, 5 };

        var out = list.Where(x => x == 2).Take(2).ToList();
    }

Is the number of iterations 3 (as the second two is in index 2) or 7 (total number of elements)?

Thanks

Yes, stops.

You can see this clearly by rewriting the code as follows:

var result = list.Where(x =>
    {
        Console.WriteLine("Where: " + x);
        return x == 2;
    })
    .Take(2).ToList();

list will be iterated by the Where function, returning only matching items.
Where will be iterated by Take , which stops after 2 results.
Take is fully iterated by ToList

So the end result is that the iteration of list is stopped by Take at the second item of 2.

You can easily check it yourself. Let's test the hypothesis that 9 is reached (ie at least 4 items has been iterated):

var result = list
  .Where(x => x == 2)        // your query under test
  .Take(2)
  .Select(item => item != 9  // business as usual for the first 3 items
     ? item                  // throw exception on the 4th
     : throw new Exception("Strange execution: 9 (4th item) has been scanned"))
  .ToList();                 // materialization executes the query

Run it and you'll see that 4th item ( 9 ) has not been taken: no exception has been thrown.

I think the most convincing (and simple) answer is to look at the source code of TakeIterator that runs when Take is called:

static IEnumerable<TSource> TakeIterator<TSource>(IEnumerable<TSource> source, int count) 
{
    if (count > 0) {
        foreach (TSource element in source) {
            yield return element;
            if (--count == 0) break; // Yep, it stops after "count" iterations
        }
    }
}

If you write some test code with your own IEnumerable and IEnumerator, it will be easy to see what happens.

class MyCollection : IEnumerable<int>
{
    public List<int> Data {get; set;} = new List<int>() { 2, 3, 2, 9, 10, 2, 5 };

    public IEnumerator<int> GetEnumerator()
    {
         return new MyEnumerator()
         {
             Data = this.Data,
         };
    }
}

And the enumerator:

class MyEnumerator : IEnumerator<int>
{
    private int index = -1;
    public List<int> Data {get; set;}

    public void Reset()
    {
        this.index = -1;
    }

    public bool MoveNext()
    {
        ++this.index;
        return this.index < this.Data.Count;
    }

    public int Current
    {
        get
        {
            int returnValue = this.Data[this.index];
            Debug.WriteLine("[{0}] {1}", this.index, returnValue);
            return returnValue;
        }
    }
}

Test code:

void Main()
{
    MyCollection collection = new MyCollection();
    var out = collection.Where(x => x == 2).Take(2).ToList();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM