LINQ deferred execution when updating a property

Advertisements

I have the following C# code:

#nullable enable

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main()
    {
        var userNames = new List<string>{"Jon Doe", "Jane Doe"};
        var persons = userNames.Select(x => new Person{ Name = x });
        
        foreach (var person in persons)
        {
            person.FamilyName = person.Name.Split(' ').Last();
        }

        var familyNameList = persons.Select(x => x.FamilyName).ToList();
        Console.WriteLine(string.Join(", ", familyNameList));
    }

    private class Person
    {
        public required string Name { get; init; }

        public string? FamilyName { get; set; }
    }
}

#nullable disable

The console outputs:

 , 

And upon inspecting the familyNameList list, I find that it only contains null values.

I understand that deferred execution is in play, but why is the FamilyName not set in the foreach loop?

And if the Person is not instantiated before .ToList() is called, then why is there no NullReferenceException thrown when trying to set the FamilyName property?

Link to .NET Fiddle: https://dotnetfiddle.net/VMKXEw

>Solution :

In short – materialize the enumerable:

var persons = userNames.Select(x => new Person{ Name = x }).ToList();

I understand that deferred execution is in play, but why is the FamilyName not set in the foreach loop?

It is set but the results are effectively discarded. I.e. the following:

var familyNameList = persons.Select(x => x.FamilyName).ToList();
Console.WriteLine(string.Join(", ", familyNameList));

Will iterate a completely new collection of freshly created persons. LINQ is lazy and the actual execution is deferred until one of the materializable operations (Count, foreach, ToList, First, etc.). And the whole enumerable will be enumerated for every such operation (with the exception of some optimizations like for Count when the passed IEnumerable<T> is actually materialized and has count).

Just add a side-effect to the original enumerable:

var persons = userNames.Select(x =>
{
    Console.WriteLine(x);
    return new Person{Name = x};
});

And the output will speak for itself.

Leave a ReplyCancel reply