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 avancé en C# avec opérateurs et optimisation des performances

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.

csharp
// 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.

csharp
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.

csharp
// 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.

csharp
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.

csharp
// 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.

csharp
// 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 = 200

Prê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.

csharp
// 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.

csharp
// É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.

csharp
// 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.

csharp
// 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.

csharp
// 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 >1

Création d'Opérateurs LINQ Personnalisés

Les méthodes d'extension permettent de créer des opérateurs LINQ réutilisables.

csharp
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é.

Défi du jour

Tu saurais repérer le bug en .NET ?

Un vrai bout de code, un bug caché, une tentative par jour. Sans compte pour essayer.

Anthony Fillion-Maillet

Écrit par

Anthony Fillion-Maillet

Fondateur 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

#csharp
#linq
#performance
#interview
#dotnet

Partager

Articles similaires