Test technique C# : exercices corrigés sur LINQ, async et la durée de vie des objets

Préparez un test technique C# avec un export de commandes : prédisez LINQ, corrigez async, vérifiez l’annulation et expliquez la durée de vie des objets.

Author: PracHub

Published: 10/11/2026

Test technique C# : exercices corrigés sur LINQ, async et la durée de vie des objets

October 11, 2026

Quick Overview

Trois exercices C# corrigés suivent un export de commandes : requête LINQ différée, session fermée avant la fin de sa tâche et annulation coopérative. Dix vérifications natives .NET contrôlent la sélection et la libération, avec des limites explicites sur les données distantes et la durabilité.

Backend EngineerFree

Quelques lignes de C# peuvent vous demander trois raisonnements différents : quand les données sont-elles lues, quand la tâche se termine-t-elle et qui possède la ressource utilisée ? Avant de proposer une architecture, prédisez un résultat observable et montrez la correction qui le change.

Ce guide utilise un programme original d’export de commandes. Suivez une requête LINQ, une lecture asynchrone suspendue et la libération de sa session. Pour prolonger le sujet, travaillez Assemble async stream reads into objects, puis expliquez les hypothèses nécessaires avant d’adapter la solution en C#.

Périmètre des preuves : Microsoft documente les mécanismes cités ; le programme et ses données sont nos exercices. Les dix vérifications ont été exécutées avec le SDK .NET 10.0.401 sur macOS. Elles couvrent LINQ to Objects et une session en mémoire contrôlée par un signal, sans base de données, serveur HTTP ni export durable. Aucun témoignage de candidat ne permet ici d’annoncer le contenu d’un test d’entreprise.

Un export de commandes relie requête différée, attente et durée de vie de la session

Exercice LINQ : prédire le moment de lecture

La liste initiale contient deux commandes payées, d’identifiants 1 et 2. Chaque commande est un record immuable dans cet exercice ; la liste elle-même reste modifiable. Lisez les instructions dans l’ordre, puis écrivez les valeurs affichées sans exécuter le programme.

var orders = new List<Order> { new(1, true), new(2, true) };
var query = orders.Where(o => o.Paid).Select(o => o.Id);
var snapshot = query.ToArray();
orders.RemoveAt(0); orders.Add(new(3, true));
Console.WriteLine(string.Join(",", query));
Console.WriteLine(string.Join(",", snapshot));
// record Order(int Id, bool Paid);

Le premier affichage est 2,3, le second 1,2. La variable query conserve une description du parcours, pas les identifiants déjà calculés. ToArray a parcouru cette description avant le changement de liste. Le tableau contient donc les valeurs sélectionnées à ce moment-là ; l’énumération ultérieure de query voit le contenu modifié.

La documentation Microsoft sur les requêtes LINQ distingue déclaration, exécution différée et matérialisation. Elle indique notamment que les résultats d’une requête différée peuvent changer entre énumérations. Cela fonde notre explication ; les valeurs précises viennent de notre liste et de nos vérifications natives.

Choisissez la correction selon le moment où l’export doit sélectionner ses données. Pour exporter la sélection au moment où l’utilisateur confirme, matérialisez les données nécessaires à cet instant. Pour afficher continuellement la source actuelle, une nouvelle énumération peut être voulue. Ne remplacez pas systématiquement toutes les requêtes par ToList : vous ajouteriez des allocations et pourriez charger un ensemble trop important.

Ici, nous projetons des identifiants entiers. Si vous matérialisez plutôt des références vers des objets mutables, le tableau fige la sélection des références, pas toutes leurs propriétés. Une modification ultérieure de ces objets peut encore modifier ce qu’un export lit. Expliquez si vous avez besoin d’une projection de valeurs, d’une copie plus profonde ou d’un contrat d’immuabilité.

Ne transposez pas ce résultat directement à Entity Framework. Une requête IQueryable dépend aussi du fournisseur, de la connexion, de la traduction et de l’état transactionnel. Notre démonstration n’a exécuté aucun SQL. En entretien, dites « dans cette liste en mémoire » avant de discuter ce qui changerait avec une source distante.

Exercice async : retourner une tâche ferme trop tôt la session

La seconde partie utilise une session d’export créée par open. Elle possède une méthode ReadAsync et implémente IDisposable. La lecture attend un signal du test avant de produire deux lignes. La session vérifie alors qu’elle n’a pas déjà été libérée. Ce comportement contrôlé rend le défaut reproductible.

public static Task<string[]> Bad(
    Func<ExportSession> open, CancellationToken ct) {
    using var session = open();
    return session.ReadAsync(ct);
}

Le return rend la tâche à l’appelant, puis quitte la portée du using. Il n’attend pas sa réussite. Dans notre test, la tâche est encore suspendue lorsque Dispose est appelé. Après libération du signal, la lecture constate que la session est fermée et lève ObjectDisposedException. L’appelant doit observer la tâche pour voir cette erreur.

Microsoft décrit l’instruction using comme un moyen de garantir la libération à la sortie de portée, y compris lorsqu’une exception survient. Ce mécanisme ne prolonge pas automatiquement la portée jusqu’à la fin d’une tâche simplement retournée. Le problème vient donc de la relation entre portée et opération en cours.

Une correction pour ce contrat consiste à attendre la lecture dans la portée qui possède la session. Le contrôle d’annulation avant acquisition évite aussi de créer une ressource lorsque la demande est déjà annulée. La méthode corrigée du programme est la suivante :

public static async Task<string[]> Good(
    Func<ExportSession> open, CancellationToken ct) {
    ct.ThrowIfCancellationRequested();
    using var session = open();
    return await session.ReadAsync(ct);
}

Une assertion vérifie que la session reste ouverte pendant la suspension contrôlée. Après la lecture, la méthode quitte sa portée et la libère une fois. Si la lecture échoue, la même sortie de portée libère la session et laisse l’exception observable. Nous supposons ici que l’acquisition retourne une session valide et que Dispose ne lève pas d’exception.

Retirer await peut être pertinent dans certaines méthodes qui ne possèdent aucune ressource et ne nécessitent pas ce traitement de portée. Ce n’est donc pas une règle de style générale. Pour cet exercice, l’await exprime une contrainte de durée de vie. Expliquez cette contrainte avant de parler de micro-optimisation ou de machine à états.

async ne signifie pas non plus qu’un nouveau thread traite obligatoirement l’export. Notre signal suspend une attente ; il ne constitue ni un benchmark ni un traitement CPU parallèle. Si l’entretien demande la performance, mesurez le comportement du système concerné plutôt que de déduire son débit de la présence d’un mot-clé.

Exercice d’annulation : préciser ce qui s’arrête

L’annulation .NET est coopérative : le demandeur signale l’annulation et le code doit l’observer. Ajouter un paramètre CancellationToken sans le transmettre ni le consulter ne rend pas une opération annulable. Notre lecture attend un signal avec WaitAsync(ct), ce qui permet au test d’annuler cette attente.

Dans le premier cas, le token est annulé avant l’appel. ThrowIfCancellationRequested empêche l’acquisition : le compteur de créations reste à zéro. Dans le second, la session est déjà acquise et la lecture attend. Le test annule le token, observe OperationCanceledException et vérifie que la sortie de portée a libéré la session une fois.

Ces deux résultats ne disent pas qu’un serveur distant a annulé une commande ou qu’un fichier déjà envoyé a disparu. Ils décrivent notre attente et notre ressource locale. Une opération distante peut avoir avancé avant que le client observe l’annulation. Si l’export crée un travail persistant, il faut définir séparément son identifiant, son état et la manière de consulter son résultat.

Il existe aussi une course entre réussite et demande d’annulation. Ne promettez pas que toute demande reçue avant le retour imposera forcément un résultat annulé. Notre fixture place volontairement l’annulation pendant une attente bloquée pour contrôler le cas étudié. Elle ne prouve pas un ordre universel pour toutes les bibliothèques ou tous les ordonnancements.

Évitez de transformer silencieusement une annulation en tableau vide : le lecteur pourrait croire qu’aucune commande n’était à exporter. L’appelant doit pouvoir distinguer réussite sans ligne, annulation et échec de lecture. La présentation finale à l’utilisateur dépend ensuite du contrat de l’application, notamment si une nouvelle demande a remplacé l’ancienne.

Lire la trace de propriété plutôt que compter les objets

Chemin contrôléObservation attendue
Ancienne méthode, lecture suspendueSession libérée avant la fin, puis erreur après reprise
Méthode corrigée, lecture suspendueSession encore ouverte pendant l’attente
Lecture réussieDeux lignes retournées et une libération
Annulation préalableAucune acquisition
Annulation pendant l’attenteAnnulation observable et une libération
Erreur de lecture synthétiqueIOException observable et une libération

Les dix assertions comptent aussi les deux observations LINQ et les étapes intermédiaires nécessaires pour caractériser le défaut. Le compteur appartient à une fausse session volontairement simple. Ce compteur ne mesure ni les descripteurs de fichiers du système ni les fuites éventuelles d’un pilote. Une assertion « libéré une fois » a donc un périmètre précis.

Le garbage collector gère la mémoire managée ; il ne constitue pas un rendez-vous déterministe pour fermer une ressource détenue par votre opération. Dans ce programme, la propriété est lexicale : la méthode qui appelle open ferme ce qu’elle acquiert. Une session reçue d’un autre propriétaire pourrait demander un contrat différent, que vous devez annoncer avant d’ajouter un using.

Si le nettoyage réel est asynchrone, le contrat peut utiliser IAsyncDisposable et await using. Microsoft documente cette libération asynchrone. Notre session implémente seulement IDisposable ; changer le mot-clé sans changer le type et le contrat ne démontre rien sur un nettoyage distant.

La session corrigée reste ouverte pendant await puis se ferme sur réussite, annulation ou erreur

Relier la correction au contrat d’export

Avant d’intégrer la correction dans une application, demandez quand la sélection doit être figée et quelle quantité de données elle peut contenir. Notre tableau de petits identifiants convient à une démonstration. Un export volumineux pourrait demander un parcours progressif, une limite de mémoire et un point de reprise défini, chacun avec des tests supplémentaires.

Demandez aussi ce qui rend un résultat publiable. Deux lignes en mémoire ne sont pas un fichier complet disponible au bon utilisateur. Dans une implémentation réelle, il faudrait vérifier écriture, fermeture, éventuelle publication et autorisation de téléchargement. Le programme ne fournit aucune garantie de transaction, d’atomicité ou de durabilité ; n’ajoutez pas ces mots à votre résumé de test.

L’erreur synthétique conserve son type afin que le test puisse la distinguer de l’annulation. L’interface utilisateur peut ensuite afficher un message adapté sans exposer de détails internes. Conservez un diagnostic exploitable du côté approprié. Une interception large qui retourne « succès » rendrait la démonstration plus courte, mais supprimerait l’information utile au demandeur.

Pour présenter le travail, ouvrez par le contre-exemple : « La tâche reprend après la fermeture de sa session. » Montrez ensuite l’attente dans la portée, puis les vérifications de réussite, d’annulation et d’échec. Terminez par une limite précise, par exemple l’absence de base de données. L’évaluateur peut ainsi relier chaque conclusion à une observation du programme.

Construire une vérification qui distingue les deux versions

Le signal de notre fixture empêche la lecture de terminer avant que le test examine la session. Sans cette barrière, une lecture immédiatement terminée pourrait cacher le défaut : l’ancienne méthode fermerait la session après son utilisation effective. Un test qui passe uniquement avec cette réponse rapide ne réfute donc pas le contre-exemple suspendu.

Commencez par appeler la méthode sans attendre sa fin. Vérifiez que la tâche reste incomplète et inspectez le compteur de libération. Déclenchez ensuite le signal, puis attendez le résultat ou l’exception. Cet ordre produit une trace contrôlée ; ajouter une pause arbitraire ne garantirait pas que la lecture a atteint le point que vous souhaitez tester.

Pour le scénario d’échec, la fausse lecture lève une IOException seulement après le signal. Le test vérifie à la fois le type observé et le nettoyage. Cela distingue une méthode qui nettoie correctement d’une méthode qui avale l’erreur. Le chemin d’annulation vérifie séparément la sortie de portée lorsque l’attente reçoit son token.

Si l’évaluateur change le contrat pour retourner un flux au lieu d’un tableau, reprenez la question de propriété. Une méthode ne peut pas fermer le flux puis laisser croire que le destinataire pourra le lire. Il faudra déplacer la responsabilité de libération ou consommer le flux avant le retour. Notre tableau de chaînes ne possède pas cette dépendance ouverte, ce qui explique pourquoi il peut être retourné après la fermeture de la session.

Pratiquer les mêmes compétences sur d’autres tâches

Question PracHubTravail ciblé en C#
Assemble async stream reads into objectsDéfinissez qui possède le flux et combien de temps il reste utilisable
Design async batched key-value fetcherDistinguez attente, annulation et résultat déjà produit
Name-Keyed Async Task Scheduler with Sequential Lanes, Cancel and Clear by NameExpliquez ce qu’annuler signifie pour une tâche en attente ou déjà commencée
Most Active Users in a Live Communication StreamAnnoncez la frontière entre source changeante et sélection matérialisée
Design Calendar Event CRUD with Unit TestsÉcrivez un contre-exemple reproductible avant la correction

Ces exercices voisins ne sont pas un questionnaire officiel C#. Commencez par Assemble async stream reads into objects, en annonçant vos hypothèses de propriété et d’annulation. Gardez la prédiction initiale, la trace observée et la correction côte à côte : elles rendent votre raisonnement vérifiable.

Sources and Further Reading


Comments (0)