Question

I know a lot about C# but this one is stumping me and Google isn't helping.

I have an IEnumerable range of objects. I want to set a property on the first one. I do so, but when I enumerate over the range of objects after the modification, I don't see my change.

Here's a good example of the problem:

    public static void GenericCollectionModifier()
    {
        // 1, 2, 3, 4... 10
        var range = Enumerable.Range(1, 10);

        // Convert range into SubItem classes
        var items = range.Select(i => new SubItem() {Name = "foo", MagicNumber = i});

        Write(items);  // Expect to output 1,2,3,4,5,6,7,8,9,10

        // Make a change
        items.First().MagicNumber = 42;

        Write(items);  // Expect to output 42,2,3,4,5,6,7,8,9,10
        // Actual output: 1,2,3,4,5,6,7,8,9,10
    }

    public static void Write(IEnumerable<SubItem> items)
    {
        Console.WriteLine(string.Join(", ", items.Select(item => item.MagicNumber.ToString()).ToArray()));
    }

    public class SubItem
    {
       public string Name;
       public int MagicNumber;
    }

What aspect of C# stops my "MagicNumber = 42" change from being output? Is there a way I can get my change to "stick" without doing some funky converting to List<> or array?

Thanks! -Mike

Was it helpful?

Solution

When you call First() it enumerates over the result of this bit of code:

Select(i => new SubItem() {Name = "foo", MagicNumber = i});

Note that the Select is a lazy enumerator, meaning that it only does the select when you ask for an item from it (and does it every time you ask it). The results are not stored anywhere, so when you call items.First() you get a new SubItem instance. When you then pass items to Write, it gets a whole bunch of new SubItem instances - not the one you got before.

If you want to store the result of your select and modify it, you need to do something like:

var items = range.Select(i => new SubItem() {Name = "foo", MagicNumber = i}).ToList();

OTHER TIPS

I suspect something going in the background. Most likely due to the fact the IEnumerables can only be iterated once.

Does it work if you add a 'ToList()' after the call to Select() when assigning to 'items'?

The only thing I can think of is that items.First() passes a copy of SubItem to your instead of the reference, so when you set it the change isn't carried through.

I have to assume it has something to do with IQueryable only being able to be iterated once. You may want to try changing this:

// Convert range into SubItem classes
var items = range.Select(i => new SubItem() {Name = "foo", MagicNumber = i});

to

// Convert range into SubItem classes
var items = range.Select(i => new SubItem() {Name = "foo", MagicNumber = i}).ToList();

And see if there are any different results.

You can't/shouldn't modify a collection through an enumerator. I'm surprised this doesn't throw an exception.

.First() is a method, not a property. It returns a new instance of the object in the first position of your Enumerable.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top