Back to Blog
C#

Using the C# Discard Operator Effectively

Learn how to use the C# discard (`_`) to ignore unwanted values in assignments, out parameters, tuple deconstruction, and pattern matching, with practical examples.

C#discard operatortuple deconstructionpattern matchingout parameters
C# code showing the discarded underscore character being used to ignore an out parameter, with a visual emphasis on the underscore symbol.

The C# discard, written as an underscore (_), is a way to tell the compiler that a value is intentionally ignored. When used as a discard, _ is not a normal variable: it has no readable value, and repeated assignments simply ignore each new value. Discards are useful when a method returns a value you do not need, when an out parameter is required but irrelevant, or when a tuple contains fields that are not part of the current logic.

// Return value ignored _ = int.TryParse(input, out _);

This article explains where discards fit into everyday C# code, how they behave at compile time, and where misusing them can hide bugs.

What the Compiler Does with a Discard

A discard is not a storage location. When you write _ = SomeMethod(), the compiler still evaluates the method, including all side effects, but no local variable is allocated for the returned value. The result of SomeMethod() is dropped after the call.

This is different from assigning to a variable and then never reading it: a variable can be read later by accident, and an unused local may trigger a compiler warning. A discard makes the intent explicit: the value is deliberately unneeded.

Discards can appear in several contexts:

  • assignment: _ = ComputeResult();
  • deconstruction: (_, y) = GetPoint();
  • out parameters: TryGetValue(key, out _)
  • pattern matching: case int _:
  • lambda parameters: (_, _) => ... (C# 9+)
  • switch expressions: _ => defaultValue

In each context, the underscore does not name a real variable, so you cannot read its value from a discard.

Using Discards with out Parameters

Methods like int.TryParse and Dictionary.TryGetValue use out parameters to return additional data. Sometimes you only care about the boolean return value, and the out value is meaningless in that call.

if (int.TryParse(input, out _)) { Console.WriteLine("Valid integer"); }

Here the parsed integer is discarded because only the success/failure flag matters. Using a discard is clearer than declaring a dummy variable, because a dummy variable signals that you might use it later, which invites misuse.

This pattern also works with custom methods that expose out parameters only for completeness. If the caller does not need the value, a discard keeps the call site clean.

Deconstructing Tuples with Discards

Tuples are a common way to return several values from a method. When you only need some of them, discards let you ignore the rest.

var (name, _, score) = GetUserInfo(); Console.WriteLine($"{name}: {score}");

GetUserInfo returns a three-element tuple, but the middle field is irrelevant to the current logic. The discard occupies that position, so the deconstruction still compiles.

If you do not need any of the returned fields, you can deconstruct all of them with discards:

(_, _, _) = GetUserInfo();

That is legal but rarely useful; if the point is only to trigger side effects, a direct method call is usually clearer. In day-to-day code you are more likely to discard a subset of fields.

Discards in Pattern Matching

Pattern matching uses discards to match a type without binding the matched value.

object value = ...; string description = value switch { int i when i > 0 => $"Positive {i}", int _ => "Non-positive integer", string s => s, _ => "Unknown type" };

The int _ pattern matches any integer but ignores the actual value. The standalone _ arm is a wildcard/default: it matches anything not caught by the earlier arms. The two are not the same: int _ is limited to integers, while the final _ matches every remaining value.

In the classic switch statement, the fallback branch is still usually written as default:; type patterns such as case int _: are also valid. In a switch expression, _ is the fallback arm.

Discards as Lambda Parameters

C# 9 introduced support for discards as lambda parameters, which is useful when a delegate signature has parameters you do not need.

Action<int, int> handler = (_, _) => Console.WriteLine("Button clicked");

Both parameters are discarded, but the lambda still has the required Action<int, int> signature. Before C# 9, two parameters named _ could not be declared in the same lambda; using discards removes that conflict.

Performance, Maintainability, and Tradeoffs

Discards do not create a local variable, so no storage is allocated for the ignored value. That is rarely the reason to use them, but it means there is no performance downside compared with otherwise ignoring a result.

The more important effect is maintainability. A discard says: "I know this value exists and I do not need it." A dummy variable such as var unused = ... leaves ambiguity. Future readers may wonder whether the variable was meant to be used later, and an unused local can trigger analyzer warnings.

That said, overusing discards can hurt clarity. If you discard many values, you lose the ability to reference them later without changing the call or deconstruction. Discards are also positional: in a deconstruction, you must know the order of the tuple elements. If that order changes, a discard may silently ignore a different field than intended. This is a real maintainability risk, especially in large codebases.

A discard can also be confused with the underscore used as a digit separator in numeric literals, such as 1_000_000. Those underscores are part of the literal and have nothing to do with discards. Context makes the meaning clear, but it is worth noting when reading code.

Where Discards Can Cause Problems

Discards do not suppress exceptions or change runtime behavior. For example, assigning a returned Task to a discard without awaiting it is the same as ignoring the task: if the task faults, you may never observe the exception.

_ = FireAndForgetAsync(); // dangerous if the Task's exceptions matter

Instead, await the task when you can:

await FireAndForgetAsync();

A discard also cannot make an out value available later. If you discard the out value from a TryGetValue call, you are choosing not to keep the retrieved data. That can be fine, but it is not the same as saving the value for later use.

Practical Decision Criteria

Use a discard when:

  • The return value is genuinely irrelevant to the current logic.
  • An out parameter exists only to satisfy an API contract.
  • A tuple element is not part of the current computation.
  • A pattern needs to match a value without using it.
  • A lambda must match a delegate signature but does not need its parameters.

Do not use a discard when:

  • The value might be needed later in the method.
  • The value carries important error information, such as an error code in an out parameter.
  • The returned Task or other asynchronous value is the only way to observe completion or exceptions.

If you later find yourself changing a discard into a real variable, that is normal. The discard makes it explicit what you originally ignored, which makes the change safe and obvious.

Example: Discarding Only What You Need

A realistic use is a method that only checks whether a key already exists and ignores the existing value:

public bool TryAddUser(int id, string name, IDictionary<int, User> users) { if (users.TryGetValue(id, out _)) { return false; } users[id] = new User(name); return true; }

The code cares only about whether id is present. out _ says clearly that the stored User object is irrelevant to this check. The discard also keeps the code simple: there is no unused variable left behind to suggest that the existing value matters.

C# Discard: Practical Usage and Code Examples | RYUSLOG DEV