Back to Blog
C#

C# Discard Underscore: Syntax and Practical Usage

c# discard underscore: Explains how the C# discard underscore works in out arguments, pattern matching, deconstruction, and lambdas, with scope and allocation notes.

C#discardspattern matchingdeconstructionout parameterslambda parameters
Editorial illustration of the C# discard underscore: a value flows into an underscore symbol and is dropped while the surrounding code continues.

The _ character in C# is a discard when the compiler sees it in a discard position. The phrase c# discard underscore refers to exactly this behavior: using _ as a placeholder that accepts a value but never stores it. Unlike a declared variable, a discard cannot be read, and the compiler enforces that rule. This makes discards useful for out arguments, pattern matching, deconstruction, and lambda parameters where the value is irrelevant to the surrounding logic.

What a Discard Is and How the Compiler Treats It

A discard is a write-only placeholder. When you write _ = someValue;, the expression on the right is evaluated for its side effects, and the result is dropped. The compiler does not create a local variable slot, and the name _ does not become a readable identifier in the current scope.

int result = Compute(); _ = result; // value is discarded // Console.WriteLine(_); // compile error: the name '_' does not exist

The same rule applies in every discard position. The compiler recognizes _ as a discard only in specific syntactic contexts, summarized below:

PositionExampleMinimum C# version
out argumentint.TryParse(input, out _)7.0
pattern wildcard_ => ... in a switch expression7.0
deconstruction targetvar (_, y) = point;7.0
lambda parameter(_, _) => Handle();9.0

If you try to read _ in any of these contexts, the compiler reports that the name does not exist. That is the defining property of a discard: it accepts a value but exposes nothing.

Ignoring out Parameters with Discards

The most common use of a discard is to ignore an out parameter that the calling code does not need. Parsing methods are the typical example:

if (int.TryParse(input, out _)) { // parsing succeeded; the parsed value is not needed }

Without a discard, you would declare a variable that is never used:

int parsed; if (int.TryParse(input, out parsed)) { // parsed is never read }

The discard version communicates intent directly: the code cares only about the boolean result. It also avoids analyzer warnings about assigned-but-unused variables, which some teams treat as errors.

Wildcard Patterns in Switch and is Expressions

In pattern matching, _ is the wildcard pattern. It matches any value and binds nothing. This is the fallback arm in a switch expression:

string Describe(object value) => value switch { int i => $"integer {i}", string s => $"string {s}", _ => "unknown type" };

The discard pattern also appears inside property patterns when a specific member is irrelevant:

if (point is { X: 0, Y: _ }) { // X is zero, Y can be anything }

Here Y: _ matches any Y value without introducing a name for it. The pattern succeeds as long as X is zero.

Skipping Positions in Deconstruction

When you deconstruct a tuple or a type that implements Deconstruct, a discard lets you skip positions you do not need:

var (name, _, age) = GetPerson();

The second element is evaluated and then dropped. The same works with positional records:

var (_, y) = GetCoordinates();

Deconstruction still invokes the code that produces the discarded members. A discard does not skip the work; it only avoids storing the result. If producing the discarded value is expensive, the discard does not help with that cost.

Discard Parameters in Lambdas

C# 9.0 added support for discards as lambda parameters. This is useful when a delegate signature requires more parameters than the body uses:

button.Click += (_, _) => HandleClick();

Both parameters are discarded. Before C# 9, you had to name the parameters even though they were unused:

button.Click += (sender, e) => HandleClick();

The discard form makes it explicit that the handler does not depend on the event arguments. The same pattern appears in LINQ projections that need an index parameter:

var selected = items.Select((item, _) => item.Name);

When _ Is a Real Variable Instead of a Discard

A discard is not a variable, but the name _ can still be a real variable in some scopes. If you declare var _ = 5; in a method, then _ is an ordinary variable for the rest of that method. Any later use of _ in a discard position changes meaning: assignments write to the variable instead of being discarded.

var _ = 5; _ = Compute(); // writes to the variable, not a discard

This is a common source of confusion when refactoring code that already uses _ as a conventional placeholder name. The compiler treats _ as a discard only when no variable named _ is in scope. If a variable named _ exists, the assignment targets that variable, and the discarded value is silently stored.

Storage and Allocation Behavior of Discards

A discard does not allocate storage. When you write out _, the compiler does not reserve a local slot for the discarded value. For value types this removes a stack slot; for reference types it avoids keeping a reference alive beyond the statement.

The practical effect is small in most code, but there is one meaningful case: discards prevent accidental rooting of large objects. If you assign a large object to a named variable that stays in scope, the object remains reachable until the variable goes out of scope. A discard drops the reference immediately, allowing the garbage collector to reclaim the object earlier.

Do not expect measurable performance gains from replacing unused variables with discards. The JIT may already eliminate unused assignments in hot paths. The primary benefit is clarity and the removal of unused-variable warnings, not throughput.

Common Mistakes and Edge Cases

One mistake is using _ as a discard in a scope where _ is already declared as a variable. The assignment silently targets the variable instead of discarding the value. This can hide bugs because the code looks like it is ignoring a result when it is actually mutating a variable.

Another edge case is the standalone assignment _ = value; in a scope with no _ variable. In that situation the compiler treats it as a discard, but the behavior is easy to misread. The safest approach is to avoid declaring a variable named _ in any scope where you also use discards.

Discards cannot be read, so you cannot use _ in an expression that requires a value:

int x = _; // compile error

This is intentional. The compiler enforces that a discard has no readable value, which is what distinguishes it from a conventional variable. If you need to read the value later, use a named variable instead of a discard.

c# discard underscore: Practical Usage and Code Examples | RYUSLOG DEV