Search code examples
c#linqprojection

Linq Select projections with calculated values


What I've noticed when using Select with an anonymous function or other value that's calculated at runtime is that every time you access the output IEnumerable object is that the projection recalculates the value. Example:

public class A
{
    public string Name { get; set; }
    public string Addr { get; set; }
}
public class B
{
    public A Whatever {get;set;}
    public int Id {get;set;}
}

Random rand = new Random();
IEnumerable<B> listOfBs = someListOfA.Select( x => new B()
{
    Id = rand.Next(),
    Whatever = x
});

What I've noticed is that every time I parse through the list listOfB and use the Id property, the Id is reevaluated with another rand.Next().

foreach( B b in listOfBs )
{
  doSomething( b.Id );
}

I haven't seen what would cause this in the documentation on C# projections. Its almost as if Select generates an anonymous function that is reevaluated every time its accessed. So, two questions:

  1. what is this behavior I'm seeing.
  2. how can I avoid this, but still be able to convert a list of one type to another list of another type.

Let me know if my bad example code conveys my point.


Solution

  • It's expected and that's because most LINQ methods are lazy - that means they don't enumerate the source until results are needed. In you particular scenario listOfBs is not really a materialized collection of B objects. Instead, it's a definition on how to translate someListOfA into a collection of B objects. Select returns an object which implements IEnumerable<T> and stores both reference to the source collection and the projection delegate. The projection is done when results are needed, e.g. when you iterate over the collection in foreach. If you iterate multiple times the projection will be performed multiple times. Which is exactly what you're seeing.

    Call ToList or ToArray to materialize the results immediately:

    IEnumerable<B> listOfBs = someListOfA.Select( x => new B()
    {
        Id = rand.Next(),
        Whatever = x
    }).ToList();