LINQ permet d'effectuer simplement, de façon compacte et intuitive des opérations sur des séquences. Certaines opérations peuvent cependant être déroutantes.

LINQ (Language-Integrated Query) est le nom d’un ensemble de technologies basé sur l’intégration de fonctions de requête directement dans le langage C#.

LINQ permet notamment d'effectuer simplement, de façon compacte et intuitive des opérations sur des séquences comme des filtres, des projections, des partitionnements, des agrégations, ... Ci-dessous un exemple d'agrégation de chaînes de caractères:

string[] words    = new string[] {"Hello", "world!"};
string   sentence = words.Aggregate((previous, next) => $"{previous} {next}");
Console.WriteLine(output); // Hello world!

Cette opération peut cependant être parfois déroutante:

string[] colors = new string[] { "red", "green", "blue", "pink", "orange", "brown" };
string   output = colors.Where(c => c.StartsWith("a"))
                        .Aggregate((p, n) => $"{p}, {n}");
// throws InvalidOperationException: "Sequence contains no elements."

Dans l'exemple ci-dessus la ligne 2 lève une exception InvalidOperationException qui nous informe que la séquence ne contient aucun élément. On aurait pu s'attendre plutôt à ce que le filtre (clause Where) retourne une séquence vide, mais non.

La fausse-bonne-idée serait de rajouter du code intermédiaire non-LINQ afin de vérifier si le résultat du filtre contient des éléments.

// NE PAS REPRODUIRE !!!
string[] colors = new string[] { "red", "green", "blue", "pink", "orange", "brown" };
IEnumerable<string> tmp = colors.Where(c => c.StartsWith("a"));
if (tmp.Any()) {
  Console.WriteLine(tmp.Aggregate((p, n) => $"{p}, {n}");
} else {
  Console.WriteLine("No color starts with an 'a'.");
}
// No color starts with an 'a'.

LINQ permet de résoudre ce problème (auquel on ne se serait pas implicitement attendu) grâce à l'utilisation de la méthode DefaultIfEmpty :

string[] colors = new string[] { "red", "green", "blue", "pink", "orange", "brown" };
string?  output = colors.Where(c => c.StartsWith("a"))
                        .DefaultIfEmpty()
                        .Aggregate((p, n) => $"{p}, {n}");
Console.WriteLine(output); // 

Dans l'exemple ci-dessus la valeur de output est null. Il est possible de passer en argument la valeur par défaut attendue :

string[] colors = new string[] { "red", "green", "blue", "pink", "orange", "brown" };
string   output = colors.Where(c => c.StartsWith("a"))
                        .DefaultIfEmpty(string.Empty)
                        .Aggregate((p, n) => $"{p}, {n}");
Console.WriteLine(output); // 

OK, visuellement ça ne change pas grand chose. On peut cependant noter que le type de retour n'est plus string? mais string. Si vous n'êtes pas familier des types références nullable, je vous invite à consulter cet article. L'exemple suivant est plus visuel :

string[] colors = new string[] { "red", "green", "blue", "pink", "orange", "brown" };
string   output = colors.Where(c => c.StartsWith("a"))
                        .DefaultIfEmpty("No color starts with an 'a'.")
                        .Aggregate((p, n) => $"{p}, {n}");
Console.WriteLine(output); // No color starts with an 'a'.

La technique a ses raisons que la raison ignore... 😋

Références