sǝɯɐſ
sǝɯɐſ

Reputation: 2528

Modifying a property of an object in a collection

I have a collection of objects, and want to modify a property on an object in that collection. If I have a single object, my ChangeStuff method works fine, and the object is modified when returning from the method. (First 4 lines in Main)

However, when iterating through the collection, I guess I'm missing something, as my changed values seem to be stuck in the scope of the foreach loop.

I shouldn't have to pass the objects by ref (or using an out parameter), since I'm not returning a new value.

Sorry for the big code chunk, but it's about as simplified as I can make it and still demonstrate my issue.

    class foobar
    {
        public string string1;
        public string string2;
    }

    static void Main(string[] args)
    {
        /***** THIS WORKS!! *****/
        foobar singleFb = new foobar { string1 = "foo2", string2 = "bar2" };
        ChangeStuff(singleFb);
        Console.WriteLine(singleFb.string1 + ", " + singleFb.string2);

        Console.ReadLine(); //pause to read output

        /***** THIS DOESN'T WORK!! *****/
        List<foobar> myList = new List<foobar> { new foobar {string1 = "foo1", string2 = "bar1"}, new foobar {string1 = "foo2", string2 = "bar2"}, new foobar {string1 = "something else", string2 = "something else again"} };
        IEnumerable<foobar> fbs = myList.Where(x => x.string1.StartsWith("foo"));

        ChangeStuff(fbs);

        foreach (foobar fb in fbs)
        {
            Console.WriteLine(fb.string1 + ", " + fb.string2);
        }
        Console.ReadLine(); //pause to read output
    }

    static void ChangeStuff(IEnumerable<foobar> fbs)
    {
        foreach (foobar fb in fbs)
        {
            ChangeStuff(fb);
        }
    }

    static void ChangeStuff(foobar fb)
    {
        if (fb.string1.Contains("2"))
            fb.string1 = "changed!";
    }
}

What do I need to change in order to modify an object in a collection?


Edit: Also, just noticed that my collection is actually completely missing "foo2" when it comes back... Weird. I'm actually using IQueryable in my application, and haven't experienced this problem. ie. I have all the objects, they just aren't correctly modified. Not sure what's going on here...


Edit 2: Thank you for your answers, it makes perfect sense now. If I change my ChangeStuff method as follows, it works as I would expect:

    static void ChangeStuff(foobar fb)
    {
        if (fb.string2.Contains("2"))
            fb.string2 = "changed!";
    }

Upvotes: 1

Views: 601

Answers (2)

McGarnagle
McGarnagle

Reputation: 102743

The result you are seeing is due to the lazy-load nature of IEnumerable<T>. The second time you enumerate over "fbs", the property "string1" has changed so that it no longer matches the Where predicate.

If you enumerate the IEnumerable immediately and store the result in a concrete list (eg, by using ToList() or ToArray(), then the second enumeration will show the result you expect:

IEnumerable<foobar> fbs = myList.Where(x => x.string1.StartsWith("foo")).ToList();

Here's what happens in the original version of the code:

List<foobar> myList = new List<foobar> { new foobar { string1 = "foo1", string2 = "bar1" }, new foobar { string1 = "foo2", string2 = "bar2" }, new foobar { string1 = "something else", string2 = "something else again" } };
IEnumerable<foobar> fbs = myList.Where(x => x.string1.StartsWith("foo"));

// here the enumeration yields objects #1 and #2
// object #2 has its "string1" property modified to "changed!"
ChangeStuff(fbs);

// here the enumeration is re-evaluated, and now object #2 no longer matches the predicate
// only object #1 ("foo1") is output
foreach (foobar s in fbs)
{
    Console.WriteLine(s.string1);
}

Upvotes: 7

Servy
Servy

Reputation: 203804

The most important thing to remember when dealing with LINQ is that the methods don't return the results of a query, they return an object that represents the query itself. It is when the sequence is enumerated that the query is executed and the items returned.

fbs is a query to get all of the items where the first string starts with foo. It is not a collection of the first two items, even though those are the items that would be returned if you execute that query when you first defined it.

The first time you execute the query and iterate the results you get back two items and try to change them both, only one actually gets changed by ChangeStuff. You then execute the query again to print the results, but now the changed item doesn't meet the query's criteria, and so isn't returned. Only the first item, that wasn't changed, is returned. If you iterate the list, and not your query, you'll see your changed item.

Upvotes: 5

Related Questions