IEnumerable<T> and .Where Linq method behaviour?

陌路散爱 提交于 2019-12-19 15:02:05

问题


I thought I know everything about IEnumerable<T> but I just met a case that I cannot explain. When we call .Where linq method on a IEnumerable, the execution is deferred until the object is enumerated, isn't it?

So how to explain the sample below :

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

Nothing bad so far! But the following test gives me an unexpected result although my knowledge regarding IEnumerable<T> and .Where linq method :

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

No item from tab will be multiplied by 2. The output will be : Amount:2 Amount:5 Amount:10 Amount:12

Does anyone can explain why after enumerating tab, I get the original value. Of course, everything work fine after calling .ToList() just after calling GenerateEnumerableTest() method.


回答1:


var tab = CTest.GenerateEnumerableTest();

This tab is a LINQ query that generates CTest instances that are initialized from int-values which come from an integer array which will never change. So whenever you ask for this query you will get the "same" instances(with the original Amount).

If you want to "materialize" this query you could use ToList and then change them. Otherwise you are modifying CTest instances that exist only in the first foreach loop. The second loop enumerates other CTest instances with the unmodified Amount.

So the query contains the informations how to get the items, you could also call the method directly:

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



回答2:


Like many LINQ operations, Select is lazy and use deferred execution so your lambda expression is never being executed, because you're calling Select but never using the results. This is why, everything work fine after calling .ToList() just after calling GenerateEnumerableTest() method:

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


来源:https://stackoverflow.com/questions/52951497/ienumerablet-and-where-linq-method-behaviour

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!