proposals/multiple-using-var-discards.md
using var _ as discardsAlternate proposal to anonymous-using-declarations.md.
Champion issue (anonymous using declarations): https://github.com/dotnet/csharplang/issues/8606
Allow multiple using var _ declarations by treating them as discards when conflicts would otherwise occur.
This follows the same conflict-detection philosophy as C# 9.0's lambda discard parameters, but applies it to using declarations - the one remaining context where developers are forced to create identifiers they don't want.
C# 8.0 introduced using declarations, allowing using to be applied directly to local variable declarations:
using var someDisposable = GetSomeDisposable();
// someDisposable is disposed at the end of the enclosing scope
However, when acquiring multiple disposables solely for cleanup (without needing to reference them), developers hit a pain point:
using var _ = GetDisposable();
using var _ = GetOtherDisposable(); // Error CS0128: A local variable named '_' is already defined
Today, the workaround is ugly numbered discards:
using var _ = GetDisposable();
using var __ = GetOtherDisposable();
using var ___ = GetYetAnotherDisposable();
// or
using var _1 = GetDisposable();
using var _2 = GetOtherDisposable();
using var _3 = GetYetAnotherDisposable();
We see these patterns extensively in real-world codebases - Roslyn itself has nearly 500 cases alone, and analysis of internal and open-source projects shows thousands of instances. This is a genuine pain point that developers are actively working around with hacks. Rather than continuing to ignore this issue, we should provide a clean, first-class solution.
This proposal makes the natural syntax just work:
using var _ = GetDisposable();
using var _ = GetOtherDisposable();
using var _ = GetYetAnotherDisposable(); // All three are discards
C# 9.0 introduced a similar conflict-detection approach for lambda parameters:
// C# 8.0 and earlier - ERROR
Action<int, int> a = (_, _) => { }; // Error CS0100: duplicate parameter '_'
// C# 9.0 and later - LEGAL (both parameters are discards)
Action<int, int> a = (_, _) => { };
This proposal applies that same philosophy to using var _ declarations.
This proposal requires no changes to C#'s syntax or grammar. This is purely a change to semantics and binding behavior.
When the compiler encounters conflicting _ declarations that would produce CS0128 in using var declarations
(using var _), it treats them as discards instead.
This transformation applies ONLY to using var _ declarations. Regular local variable declarations (var _ without
using) are NOT affected - developers should use normal discard syntax (_ = ...;) for those cases. Lambda parameters
already have their own discard behavior from C# 9.0 and remain independent.
The detection is purely syntactic during binding:
_ declarations within a local variable declaration spaceusing var declarationsMultiple using var _ declarations in the same scope become discards:
void Method()
{
using var _ = GetDisposable();
using var _ = GetOtherDisposable();
using var _ = GetYetAnotherDisposable(); // All three are discards
}
A single using var _ with no conflicts remains a named variable (backward compatibility):
using var _ = GetDisposable(); // Named variable (current behavior preserved)
Lambda parameters already have their own discard behavior from C# 9.0, which operates independently. Lambdas and local functions create separate declaration spaces:
void Method()
{
using var _ = GetDisposable();
using var _ = GetOther(); // Both are discards (same scope)
Action a = () =>
{
using var _ = GetThird(); // Named variable (separate scope)
};
}
Method and local function parameters are externally visible signatures and never participate in this transformation. A
method or local function parameter named _ conflicting with a using var _ remains an error:
void Method(int _) // Parameter must stay named (externally visible)
{
// ERROR CS0128: single using var _ doesn't get special treatment, conflicts with parameter
using var _ = GetDisposable();
}
The existing rule states that local variable declaration spaces may be nested, but it is an error for conflicting declarations to have the same name (producing CS0128). This error is detected syntactically during binding.
Add the following exception to this rule:
However, when multiple
using vardeclarations would conflict solely because they all use the identifier_, then all such conflicting declarations shall be treated as discards (§9.2.9.2) instead. The identifier_introduces no name into any declaration space, and the value cannot be referenced.Note: This rule applies only when conflicts would occur. A single
using var _with no conflicts retains its meaning as a named variable for backward compatibility.
This clarification may not be necessary if the above section is sufficiently clear, but can be added if desired:
Method and local function parameters are externally visible and must remain named identifiers. When a method or local function parameter named
_would conflict with a using declaration named_, the conflict remains a compile error.
Update the discard specification:
A discard is indicated by the identifier
_in the following contexts:
- [existing contexts...]
- A using var declaration (
using var _) when multiple such declarations with the same identifier would conflict in the same local variable declaration space (§7.3)
Add clarification for using declarations:
When multiple
using var _declarations would conflict within the same local variable declaration space, the conflict resolution rules in §7.3 apply.
using IDisposable _)?This proposal currently specifies using var _ only. An alternative would be to also support explicitly-typed using
declarations like using IDisposable _.
Arguments for var _ only:
var _ is the closest analog to discard syntax in the variable declaration spacevar _ and Type _Arguments for supporting both var _ and Type _:
varusing var _ = GetDisposable();
using IDisposable _ = GetOtherDisposable(); // Would both become discards
Recommendation: Start with var _ only. If explicit types prove to be a significant pain point, they can be added
later without breaking changes.
_ and using var _ unify?This proposal keeps using var _ discard detection separate from C# 9.0's lambda parameter discard detection. This
means:
Action a = _ =>
{
using var _ = GetDisposable(); // ERROR CS0128 (parameter and using var conflict)
};
Arguments for keeping them separate:
Arguments for unifying:
_ conflicts togetherAction a = _ =>
{
using var _ = GetDisposable(); // Both parameter and using var become discards
};
Action<int, int> b = (_, _) =>
{
using var _ = GetDisposable(); // All three become discards
};
Recommendation: Start with separate detection for simplicity. The LDM can decide if unification is desirable.
Ideally, _ would be treated universally as a discard in all contexts. However, achieving that ideal would require
breaking changes to existing code where _ is used as a regular identifier, and would introduce conflicts with using
alias directives at the top level (using _ = ...;). The latter conflict is not hypothetical - we've observed using _
in using aliases in several real codebases. Given C#'s strong commitment to compatibility, we're unlikely to get there
soon without severe breaks.
This proposal takes the proven pattern from C# 9.0 lambda discards and extends it minimally to solve the specific pain point with using declarations. It's non-breaking, lightweight, and focused on the one scenario where developers are forced to create unwanted identifiers.
Importantly, using var _ is already in widespread use in existing codebases - developers have been working around the
CS0128 limitation with numbered discards (_1, _2, etc.) for years. This proposal simply makes that existing pattern
less painful and more natural.
Future Compatibility: This proposal does not conflict with a hypothetical future where C# might treat _
universally as a discard. If that day comes, this feature would simply become a subset of the broader change.
Context-dependent meaning: The meaning of using var _ changes based on whether conflicts exist. However:
Asymmetry with regular locals: using var _ gets special treatment while var _ does not. However:
using requires a declaration; regular code doesn't (use _ = ...; instead)var _ declarationsExtend this to all local variable declarations, not just using declarations:
var _ = ComputeSomething();
var _ = ComputeOther(); // Would be legal
However, this is unnecessary. Developers can and should write:
_ = ComputeSomething();
_ = ComputeOther();
The pain point only exists where syntax requires a declaration (using declarations and lambda parameters). Making this change broadly would add complexity without addressing a real problem.
IDEs could provide analyzers that suggest converting numbered discards (_1, _2) in using declarations to the
using var _ pattern when multiple are present. However, no compiler diagnostics are proposed - this is purely
enabling currently-illegal code to work naturally.