Перестает ли Take(x) в Linq перечислять при взятии x объектов?

#c# #linq

#c# #linq

Вопрос:

Например, если у меня есть этот код:

     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();
    }
 

Является ли количество итераций 3 (поскольку вторые две находятся в индексе 2) или 7 (общее количество элементов)?

Спасибо

Ответ №1:

Да, останавливается.

Вы можете ясно увидеть это, переписав код следующим образом:

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

Ответ №2:

list будет повторяться Where функцией, возвращая только совпадающие элементы.
Where будет повторяться Take , который останавливается после 2 результатов.
Take полностью повторяется с помощью ToList

Таким образом, конечным результатом является то, что итерация list останавливается Take на втором элементе из 2.

Ответ №3:

Вы можете легко проверить это самостоятельно. Давайте проверим 9 достигнутую гипотезу (т.Е. Было повторено не менее 4 элементов):

 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
 

Запустите его, и вы увидите, что 4-й элемент ( 9 ) не был принят: исключение не было выдано.

Ответ №4:

Я думаю, что наиболее убедительный (и простой) ответ — посмотреть на исходный код TakeIterator , который запускается при Take вызове:

 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
        }
    }
}
 

Ответ №5:

Если вы напишете какой-нибудь тестовый код с вашими собственными IEnumerable и IEnumerator, будет легко увидеть, что произойдет.

 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,
         };
    }
}
 

И перечислитель:

 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;
        }
    }
}
 

Тестовый код:

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