Home > Back-end >  IEnumerable<T> from Enumerable.FromRange().Select() vs ToList()
IEnumerable<T> from Enumerable.FromRange().Select() vs ToList()

Time:02-08

This really stumped me, as I expected 'pass by reference' behavior. I expected this code to print "5,5,5", but instead it prints "7,7,7".

IEnumerable<MyObj> list = Enumerable.Range(0, 3).Select(x => new MyObj { Name = 7 });
Alter(list);
Console.WriteLine(string.Join(',',list.Select(x => x.Name.ToString())));
Console.ReadLine();

void Alter(IEnumerable<MyObj> list)
{
    foreach(MyObj obj in list)
    {
        obj.Name = 5;
    }
}

class MyObj
{
    public int Name { get; set; }
}

Whereas this prints "7,7,7" as expected.

IEnumerable<MyObj> list = Enumerable.Range(0, 3).Select(x => new MyObj { Name = 7 }).ToList();
Alter(list);
Console.WriteLine(string.Join(',',list.Select(x => x.Name.ToString())));
Console.ReadLine();

void Alter(IEnumerable<MyObj> list)
{
    foreach(MyObj obj in list)
    {
        obj.Name = 5;
    }
}

class MyObj
{
    public int Name { get; set; }
}

Obviously this is a simplified version of the actual code I was writing. This feels a lot more like behavior I've run into with a property that instantiates a new instance of an object. Here I understand why I would get a new instance every time I reference Mine.

public MyObj Mine => new MyObj();

I was just very surprised to see this behavior in the above code, where it feels more like I've "locked in" the enumerated objects. Can anyone help me understand this?

CodePudding user response:

Select produces an object that has lazy evaluation. This means all steps are executed on demand.

So, when this line gets executed:

IEnumerable<MyObj> list = Enumerable.Range(0, 3).Select(x => new MyObj { Name = 7 });

no MyObj instance is created yet - only the enumerable that has all the information to compute what you've indicated.

When you call alter, this gets executed during iteration via foreach and all objects get 5 assigned to their properties.

But when you print it, everything gets executed again here:

Console.WriteLine(string.Join(',',list.Select(x => x.Name.ToString())));

So during execution of string.Join, brand new MyObj instances are created with new MyObj { Name = 7 } and printed.

CodePudding user response:

This feels a lot more like behavior I've run into with a property that instantiates a new instance of an object

Your feelings are correct

When you make a LINQ Select it requires you to give it a method that it will run every time the resulting enumerable is enumerated:

var enumerable = new []{1,2,3}.Select(x => new MyObj());

Every time you enumerate this it will run that mini method (x => new MyObj()) up to 3 times - I say up to, because you don't have to fully enumerate but if you do, there are 3 elements in the array so the enumeration can cause at most three invocations of the method.

That method generates a new MyObj every time it's run. LINQ Select doesn't remember anything, nor does the variable enunerable - it effectively represents a collection of value-providing methods that will be run to provide values not a collection of values themselves. This is essentially the same as your property that provides a new value every time it is called upon

Your Alter() ran the enumeration, got each new object, modified it, then it was thrown away - nothing keeps ahold of it. Select runs a method and provides a value, alter gets the value and changes it, but doesn't store it anywhere. When you enumerate again, the objects are made new again

When you put a ToList() on the end, that's different - ToList internally enumerates the enumerable, causes the methods that generate the objects to be run but critically it stashes the resulting objects in a list and gives you the list of objects. Instead of being a collection of methods that provides values, it's a collection of values that you can alter. This would be something like a property that, if the field it relies on is null, a method is run to populate it but the next time, the same field data is returned

If you then enumerate this list you're enumerating/altering data, rather than running methods, altering the returned value and throwing it away

  •  Tags:  
  • Related