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.