Discard _ en C# : out, déconstruction, pattern matching, lambdas, résultat ignoré, et le piège du fire-and-forget avec _ = Task qui masque les exceptions.

Discard _ en C# : usages utiles, pièges et lisibilité
Le discard _ en C# paraît simple : il sert à ignorer une valeur dont on n'a pas besoin. Pourtant, comme beaucoup de petites fonctionnalités de langage, sa vraie valeur dépend du contexte.
Bien utilisé, il rend l'intention plus explicite. Mal utilisé, il cache une API mal pensée, ou pire, une exception qui ne sera jamais observée.
Ce que signifie vraiment _
Un discard indique une chose précise : cette valeur existe, mais je choisis explicitement de ne pas l'utiliser. Le compilateur n'alloue pas de variable lisible : _ ne peut pas être relu ensuite.
if (int.TryParse(input, out _))
{
// Only the format is validated, the parsed value is not kept
}
Ici, _ rend l'intention très claire : on ne veut pas exploiter l'entier analysé, on veut seulement savoir si la conversion réussit.
Les cas d'usage utiles
1. Paramètres out sans conservation de la valeur
bool isValid = Guid.TryParse(id, out _);
2. Déconstruction partielle
var (_, status, lastUpdated) = GetOrderSummary(orderId);
Quand certaines informations ne servent pas, le discard évite d'introduire des variables inutiles que l'analyse de code signalerait.
3. Pattern matching
if (message is OrderCreated(_, var customerId, _))
{
// Only customerId matters here
}
Dans une expression switch, _ joue aussi le rôle du cas par défaut :
string label = status switch
{
OrderStatus.Paid => "Payée",
OrderStatus.Shipped => "Expédiée",
_ => "En cours",
};
4. Paramètres de lambda ignorés
Depuis C# 9, plusieurs paramètres d'une lambda peuvent être ignorés, ce qui est courant avec les gestionnaires d'événements :
button.Click += (_, _) => RefreshView();
app.MapGet("/health", (HttpContext _) => Results.Ok());
5. Ignorer volontairement un résultat de retour
Assigner à _ documente qu'une valeur de retour est délibérément ignorée et fait taire l'avertissement des analyseurs :
_ = dictionary.Remove(key); // the bool result is not needed
Le piège : _ = TaskAsync()
Le cas le plus dangereux est la tâche ignorée :
_ = SendWelcomeEmailAsync(user); // fire-and-forget
Ce code compile sans avertissement, mais :
- une exception levée par
SendWelcomeEmailAsyncn'est jamais observée ni journalisée ; - la requête HTTP peut se terminer, et ses services scoped (
DbContext, par exemple) être libérés, pendant que la tâche s'exécute encore ; - rien ne garantit que le travail se termine si l'application s'arrête.
Dans une application ASP.NET Core, préférez une file en mémoire (Channel<T>) consommée par un BackgroundService, ou une file de messages durable si le travail ne doit pas être perdu. Si un fire-and-forget est vraiment acceptable, encapsulez-le dans une méthode qui capture et journalise l'exception :
static async Task RunAndLogAsync(Func<Task> work, ILogger logger)
{
try
{
await work();
}
catch (Exception exception)
{
logger.LogError(exception, "Background work failed.");
}
}
_ = RunAndLogAsync(() => SendWelcomeEmailAsync(user), logger);
Le discard reste là, mais l'échec n'est plus silencieux. La gestion des tâches concurrentes est approfondie dans Task.WhenAny ou WhenEach : bien choisir, et la publication fiable de messages après une écriture dans Inbox et Outbox en .NET.
Pourquoi c'est utile
Le principal gain n'est pas de gagner quelques caractères, mais de supprimer le bruit. Au lieu d'écrire :
Guid ignored;
bool ok = Guid.TryParse(id, out ignored);
on écrit directement l'intention :
bool ok = Guid.TryParse(id, out _);
Moins de variables parasites, moins de confusion, moins de tentation de réutiliser une valeur sans vraie raison.
Un détail de compatibilité : _ peut encore être un nom de variable
Pour préserver le code ancien, _ n'est un discard que s'il n'existe pas déjà une variable nommée _ dans la portée. Ce code est donc trompeur :
var _ = ComputeTotal(); // declares a real variable named _
int.TryParse(text, out _); // now overwrites that variable
N'utilisez jamais _ comme nom de variable : les analyseurs .NET le signalent d'ailleurs. Dans une lambda à un seul paramètre, _ reste aussi un vrai paramètre, utilisable dans le corps.
Quand le discard devient un mauvais signal
Le discard ne doit pas servir à masquer un problème de conception. Si vous ignorez souvent la moitié des valeurs retournées par une méthode, le contrat mérite d'être revu.
var (_, _, _, discountCode, _, _, _) = pricingResult;
Ce code compile, mais il signale surtout que la structure retournée n'est pas adaptée au besoin du consommateur. Dans ce cas :
- revoyez le type de retour, par exemple un
recordaux propriétés nommées ; - introduisez un contrat plus ciblé pour cet appelant ;
- simplifiez l'API appelée.
Dans une déconstruction longue, _ répété plusieurs fois oblige le lecteur à reconstruire mentalement des positions anonymes. Un accès par propriété (pricingResult.DiscountCode) reste alors plus lisible.
Conclusion
Le discard _ en C# est une petite fonctionnalité, mais une bonne petite fonctionnalité : il rend explicite ce qu'on choisit de ne pas utiliser. Son meilleur usage consiste à clarifier l'intention. Son mauvais usage consiste à cacher un contrat mal pensé ou une tâche dont l'échec ne sera jamais vu.
La question n'est pas « peut-on l'utiliser ? ». La vraie question est : « est-ce que cela rend le code plus lisible, sans rien cacher d'important ? » D'autres fonctionnalités du langage sont présentées dans le parcours C# et ASP.NET Core, notamment switch et pattern matching en C#.
