IEnumerable <T>и. Где поведение метода Linq? - PullRequest
0 голосов
/ 23 октября 2018

Я думал, что знаю все о IEnumerable<T>, но я только что встретил случай, который не могу объяснить.Когда мы вызываем метод .Where linq для IEnumerable, выполнение откладывается до перечисления объекта, не так ли?

Итак, как объяснить пример ниже:

public class CTest
{
    public CTest(int amount)
    {
        Amount = amount;
    }

    public int Amount { get; set; }

    public override string ToString()
    {
        return $"Amount:{Amount}";
    }

    public static IEnumerable<CTest> GenerateEnumerableTest()
    {
        var tab = new List<int> { 2, 5, 10, 12 };

        return tab.Select(t => new CTest(t));
    }
}

Пока ничего плохого!Но следующий тест дает мне неожиданный результат, хотя мои знания относительно IEnumerable<T> и .Where метода linq:

[TestMethod]
public void TestCSharp()
{
    var tab = CTest.GenerateEnumerableTest();

    foreach (var item in tab.Where(i => i.Amount > 6))
    {
        item.Amount = item.Amount * 2;
    }

    foreach (var t in tab)
    {
        var s = t.ToString();
        Debug.Print(s);
    }
}

Ни один элемент из вкладки не будет умножен на 2. Результат будет: Количество: 2Сумма: 5 Сумма: 10 Сумма: 12

Кто-нибудь может объяснить, почему после перечисления на вкладке я получаю исходное значение. Конечно, все работает нормально после вызова .ToList() сразу после вызова GenerateEnumerableTest() метода.

Ответы [ 2 ]

0 голосов
/ 23 октября 2018
var tab = CTest.GenerateEnumerableTest();

Этот tab является запросом LINQ, который генерирует CTest экземпляров, которые инициализируются из int -значений, которые поступают из целочисленного массива, который никогда не изменится.Таким образом, всякий раз, когда вы запрашиваете этот запрос, вы получаете «те же» экземпляры (с исходным Amount).

Если вы хотите «материализовать» этот запрос, вы можете использовать ToList, а затем изменить их.В противном случае вы изменяете CTest экземпляров, которые существуют только в первом цикле foreach.Второй цикл перечисляет другие CTest экземпляры с неизмененным Amount.

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

foreach (var item in CTest.GenerateEnumerableTest().Where(i => i.Amount > 6))
{
    item.Amount = item.Amount * 2;
}

foreach (var t in CTest.GenerateEnumerableTest())
{
   // now you don't expect them to be changed, do you?
}
0 голосов
/ 23 октября 2018

Как и многие операции LINQ, Select является ленивым и использует отложенное выполнение, поэтому ваше лямбда-выражение никогда не выполняется, потому что вы вызываете Select, но никогда не используете результаты.Вот почему все работает нормально после вызова .ToList() сразу после вызова GenerateEnumerableTest() метода:

var tab = CTest.GenerateEnumerableTest().ToList();
...