-2

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.

  • 1
    Set a breakpoint inside the Select and run the program. You will see the ii++ is executed when you execute the First() – Paul Sinnema Dec 12 '20 at 22:07
  • Thanks, @PaulSinnema. `var firstElement = dataWithIds.First(); var x = firstElement.id; var y = firstElement.id;//==x. Understood!` – Erik Lien Johnsen Dec 12 '20 at 22:22
  • check the remarks of `select` doc https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.select?view=net-5.0. Deferred execution : https://learn.microsoft.com/en-us/dotnet/standard/linq/deferred-execution-lazy-evaluation – Mohammed Sajid Dec 12 '20 at 22:39

2 Answers2

2

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.

Paul Sinnema
  • 2,534
  • 2
  • 20
  • 33
1

You can use Select overload with additional index parameter for such case.

var dataWithIds = data.Select((x, i) => new
{
    id = i + 1,
    value = x
});

According your question. If local variable is defined outside LINQ query it will be automatically wrapped by compiler into hidden class with property ii and instance of this class will be reused after every First, Count, ToList, etc. (functions which enumerates). That's why you see "strange" results. It is called Closures and there are a lot of explanations What are 'closures' in .NET?

Svyatoslav Danyliv
  • 21,911
  • 3
  • 16
  • 32