Search code examples
c#linqanonymous-types

How can an instance of an anonymous type's property of a simple type not always return the same value?


I don't really know how to frame this question, but I was really puzzled when I saw this behaviour. Is it really supposed to be like this?

var data = new List<int> { 2, 4, 1 };//some dummy data for this example
var ii = 1;
var dataWithIds = data.Select(x => new
{
    id = ii++,
    value = x
});//I thought ii now would be 4, but it's still 1
var firstId = dataWithIds.First().id;//== 1, as expected. Now ii is 2
var alsoFirstId = dataWithIds.First().id;//== 2, not expected. Now ii is 3
ii = 1000;
var okMaybeItDoesNotWorkAsAnIdThen = dataWithIds.First().id;//==1000. Now ii is 1001

With new {id = ii++} I thought the id property always return the value ii had at declaration/instantiation (and then ii is incremented at declaration/instantiation), but it seems like this actually returns the value of ii at the time the property is called (and then ii is incremented when the property is called).

My intention was to create a list of some data (or really an IEnumerable of an anonymous type, and of course a bit more than in this example code), including an (auto-incrementing) ID.

I have ways around this (for example just adding .ToList()), but it would be interesting knowing why it works like this. It also took some time to find out that this was not working like I intended (and how it was working), so hopefully this can be of help to others.


Solution

  • You have to understand how LINQ works:

            var data = new List<int> { 2, 4, 1 };//some dummy data for this example
            var ii = 1;
            var dataWithIds = data.Select(x => new
            {
                id = ii++,
                value = x
            }); // Nothing is executed at this point. Only an expression tree is created in memory.
            var firstId = dataWithIds.First().id; // The First() executes the ii++ 
            var alsoFirstId = dataWithIds.First().id; // The First() executes the ii++
    

    I know it can be puzzling but it really is a beautiful construct. You can delay execution of code until all needed execution is defined. That is especially effective when querying databases. You can postpone execution until all filters, joins, whatever have been declared. At the point of execution .ToList(), .First(), etc. the request is sent to the database as one sometimes big query.