LINQ avancé en C# en 2026 : Opérateurs, Performance et Questions d'Entretien
Maîtriser les opérateurs LINQ avancés, optimiser les performances des requêtes et se préparer aux questions d'entretien technique C# avec des exemples pratiques.

LINQ (Language Integrated Query) reste en 2026 l'une des fonctionnalités les plus puissantes de C#. Au-delà des opérations basiques comme Where et Select, les développeurs expérimentés exploitent des opérateurs avancés pour écrire du code plus expressif et performant. Cet article explore les techniques avancées de LINQ, les stratégies d'optimisation des performances et les questions d'entretien les plus courantes.
Les entretiens techniques .NET testent fréquemment la compréhension de l'exécution différée de LINQ et la différence entre IEnumerable et IQueryable. Maîtriser ces concepts constitue un avantage décisif.
Opérateurs de Projection Avancés
Les opérateurs de projection transforment les données d'une forme à une autre. SelectMany aplatit les collections imbriquées, tandis que les types anonymes et les tuples offrent une flexibilité maximale.
// SelectMany pour aplatir des collections imbriquées
var allOrders = customers
.SelectMany(c => c.Orders, (customer, order) => new
{
CustomerName = customer.Name,
OrderId = order.Id,
Total = order.Total
});
// Projection avec tuples (C# 12+)
var summary = products
.Select(p => (p.Name, p.Price, Discounted: p.Price * 0.9m))
.Where(x => x.Discounted < 100);L'opérateur Zip combine deux séquences élément par élément, utile pour fusionner des données provenant de sources différentes.
var names = new[] { "Alice", "Bob", "Charlie" };
var scores = new[] { 95, 87, 92 };
var results = names.Zip(scores, (name, score) => $"{name}: {score}");
// Output: "Alice: 95", "Bob: 87", "Charlie: 92"
// Zip avec trois séquences (C# 12+)
var dates = new[] { DateTime.Now, DateTime.Now.AddDays(1), DateTime.Now.AddDays(2) };
var combined = names.Zip(scores, dates);Opérateurs d'Agrégation Personnalisés
L'opérateur Aggregate permet de créer des réductions personnalisées au-delà de Sum, Count ou Average.
// Concaténation avec séparateur personnalisé
var sentence = words.Aggregate((current, next) => $"{current} {next}");
// Aggregate avec seed et transformation finale
var stats = numbers.Aggregate(
seed: (Sum: 0, Count: 0),
func: (acc, n) => (acc.Sum + n, acc.Count + 1),
resultSelector: acc => new
{
Total = acc.Sum,
Average = (double)acc.Sum / acc.Count
});
// Calcul de variance
var variance = numbers.Aggregate(
seed: (Sum: 0.0, SumSq: 0.0, Count: 0),
func: (acc, n) => (acc.Sum + n, acc.SumSq + n * n, acc.Count + 1),
resultSelector: acc =>
{
var mean = acc.Sum / acc.Count;
return (acc.SumSq / acc.Count) - (mean * mean);
});Opérateurs d'Ensemble et de Comparaison
Les opérations d'ensemble permettent de comparer et combiner des collections de manière déclarative.
var list1 = new[] { 1, 2, 3, 4, 5 };
var list2 = new[] { 4, 5, 6, 7, 8 };
// Union, Intersection, Différence
var union = list1.Union(list2); // 1,2,3,4,5,6,7,8
var intersection = list1.Intersect(list2); // 4,5
var difference = list1.Except(list2); // 1,2,3
// ExceptBy et IntersectBy (C# 10+)
var newProducts = allProducts.ExceptBy(
existingProducts.Select(p => p.Sku),
p => p.Sku);
// DistinctBy pour éliminer les doublons par propriété
var uniqueByCategory = products.DistinctBy(p => p.Category);Partitionnement et Découpage
Les opérateurs de partitionnement divisent les collections en segments logiques.
// Chunk pour diviser en lots (C# 10+)
var batches = items.Chunk(100); // Lots de 100 éléments
foreach (var batch in batches)
{
await ProcessBatchAsync(batch);
}
// Take et Skip avec Range (C# 8+)
var page = items.Take(10..20); // Éléments 10 à 19
var lastFive = items.TakeLast(5);
var skipLastThree = items.SkipLast(3);
// TakeWhile et SkipWhile avec index
var untilLarge = numbers
.TakeWhile((n, index) => n < 100 && index < 50);Exécution Différée vs Immédiate
Comprendre l'exécution différée est fondamental pour optimiser les performances LINQ.
// Exécution différée - la requête n'est pas exécutée ici
var query = products
.Where(p => p.Price > 100)
.OrderBy(p => p.Name);
// Exécution déclenchée lors de l'énumération
foreach (var product in query) // Exécution ici
{
Console.WriteLine(product.Name);
}
// Forcer l'exécution immédiate
var cachedResults = query.ToList(); // Exécution et mise en cache
var array = query.ToArray();
var dict = query.ToDictionary(p => p.Id);
// Attention aux closures modifiées
var threshold = 100;
var filtered = products.Where(p => p.Price > threshold);
threshold = 200; // Affecte la requête!
var results = filtered.ToList(); // Utilise threshold = 200Prêt à réussir tes entretiens .NET ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
IEnumerable vs IQueryable
La distinction entre ces interfaces détermine où s'exécute la logique de requête.
// IEnumerable - filtrage en mémoire
IEnumerable<Product> memoryQuery = dbContext.Products
.AsEnumerable()
.Where(p => CustomFilter(p)); // Exécuté côté client
// IQueryable - filtrage côté base de données
IQueryable<Product> dbQuery = dbContext.Products
.Where(p => p.Price > 100); // Traduit en SQL
// Combinaison optimale
var optimized = dbContext.Products
.Where(p => p.Price > 100) // SQL: WHERE Price > 100
.AsEnumerable() // Bascule vers mémoire
.Where(p => CustomLogic(p)); // Exécuté en C#Optimisation des Performances LINQ
Plusieurs techniques permettent d'améliorer significativement les performances des requêtes LINQ.
// Éviter les énumérations multiples
var list = expensiveQuery.ToList();
var count = list.Count; // O(1) sur List
var first = list.FirstOrDefault();
// Utiliser Any() plutôt que Count() > 0
if (items.Any(x => x.IsValid)) // Court-circuit
{
// ...
}
// Préférer les surcharges avec prédicat
var firstMatch = items.First(x => x.Id == targetId);
// Plus efficace que: items.Where(x => x.Id == targetId).First()
// HashSet pour les recherches O(1)
var validIds = validItems.Select(x => x.Id).ToHashSet();
var filtered = allItems.Where(x => validIds.Contains(x.Id));
// Parallel LINQ pour CPU-bound
var processed = largeCollection
.AsParallel()
.WithDegreeOfParallelism(Environment.ProcessorCount)
.Where(x => ExpensiveValidation(x))
.Select(x => ExpensiveTransform(x))
.ToList();Expressions de Requête vs Syntaxe Fluent
C# offre deux syntaxes pour LINQ, chacune avec ses avantages.
// Syntaxe de requête (Query Syntax)
var queryStyle =
from p in products
where p.Price > 100
orderby p.Name
select new { p.Name, p.Price };
// Syntaxe fluent (Method Syntax)
var fluentStyle = products
.Where(p => p.Price > 100)
.OrderBy(p => p.Name)
.Select(p => new { p.Name, p.Price });
// Jointures plus lisibles en syntaxe de requête
var joined =
from c in customers
join o in orders on c.Id equals o.CustomerId
join p in products on o.ProductId equals p.Id
where o.Date > DateTime.Now.AddMonths(-1)
select new { c.Name, o.Date, p.ProductName };LINQ et Async/Await
L'intégration de LINQ avec les opérations asynchrones requiert une attention particulière.
// ToListAsync avec Entity Framework
var products = await dbContext.Products
.Where(p => p.IsActive)
.ToListAsync();
// Traitement asynchrone de chaque élément
var tasks = items.Select(async item =>
{
var result = await ProcessAsync(item);
return result;
});
var results = await Task.WhenAll(tasks);
// IAsyncEnumerable (C# 8+)
await foreach (var item in GetItemsAsync())
{
await ProcessAsync(item);
}
// LINQ sur IAsyncEnumerable avec System.Linq.Async
var filtered = await asyncEnumerable
.Where(x => x.IsValid)
.Take(10)
.ToListAsync();Questions d'Entretien Fréquentes
Les entretiens techniques .NET incluent souvent des questions sur LINQ.
// Q: Quelle est la sortie de ce code?
var numbers = new List<int> { 1, 2, 3 };
var query = numbers.Select(n => n * 2);
numbers.Add(4);
Console.WriteLine(string.Join(", ", query));
// R: "2, 4, 6, 8" - exécution différée!
// Q: Implémenter GroupBy manuellement
public static IEnumerable<IGrouping<TKey, T>> MyGroupBy<T, TKey>(
this IEnumerable<T> source,
Func<T, TKey> keySelector)
{
var dict = new Dictionary<TKey, List<T>>();
foreach (var item in source)
{
var key = keySelector(item);
if (!dict.ContainsKey(key))
dict[key] = new List<T>();
dict[key].Add(item);
}
return dict.Select(kvp => new Grouping<TKey, T>(kvp.Key, kvp.Value));
}
// Q: Différence entre First() et Single()?
// First() - retourne le premier élément, exception si vide
// Single() - retourne l'unique élément, exception si 0 ou >1Création d'Opérateurs LINQ Personnalisés
Les méthodes d'extension permettent de créer des opérateurs LINQ réutilisables.
public static class LinqExtensions
{
// Batch processing avec yield
public static IEnumerable<IEnumerable<T>> Batch<T>(
this IEnumerable<T> source,
int size)
{
var batch = new List<T>(size);
foreach (var item in source)
{
batch.Add(item);
if (batch.Count == size)
{
yield return batch;
batch = new List<T>(size);
}
}
if (batch.Count > 0)
yield return batch;
}
// WhereIf conditionnel
public static IEnumerable<T> WhereIf<T>(
this IEnumerable<T> source,
bool condition,
Func<T, bool> predicate)
{
return condition ? source.Where(predicate) : source;
}
// Flatten récursif
public static IEnumerable<T> Flatten<T>(
this IEnumerable<T> source,
Func<T, IEnumerable<T>> childSelector)
{
foreach (var item in source)
{
yield return item;
foreach (var child in childSelector(item).Flatten(childSelector))
yield return child;
}
}
}Conclusion
LINQ demeure un outil indispensable pour les développeurs C# en 2026. La maîtrise des opérateurs avancés, la compréhension de l'exécution différée et l'optimisation des performances distinguent les développeurs seniors. Les patterns présentés dans cet article constituent une base solide pour les projets complexes et les entretiens techniques. La pratique régulière de ces concepts, combinée à l'analyse des requêtes générées par Entity Framework, permet d'atteindre un niveau d'expertise reconnu sur le marché.
Tu saurais repérer le bug en .NET ?
Un vrai bout de code, un bug caché, une tentative par jour. Sans compte pour essayer.

Écrit par
Anthony Fillion-MailletFondateur de SharpSkill
Développeur fullstack depuis plus de 10 ans. Il dirige SharpSkill et répond de tout ce qui y est publié.
Mis à jour le 23 août 2026
Tags
Partager
Articles similaires

.NET 10 en 2026 : Nouvelles Fonctionnalités, AOT Natif et Questions d'Entretien
Découvrez .NET 10 et C# 14 en 2026 : compilation AOT native en production, extension members, le mot-clé field, applications sans projet et questions d'entretien techniques.

Questions d'entretien C# et .NET : Guide complet 2026
Les 25 questions d'entretien C# et .NET les plus fréquentes. LINQ, async/await, dependency injection, Entity Framework et bonnes pratiques avec réponses détaillées.

Entity Framework Core : Optimisation des Performances et Bonnes Pratiques en 2026
Guide complet sur l'optimisation des performances Entity Framework Core 10 avec .NET 10. AsNoTracking, requêtes compilées, mises à jour par lot, split queries et LeftJoin.