在C#中,单个下划线 _ 在不同的上下文中有不同的含义和作用:
1. 弃元(Discard)(C# 7.0+)
最常见的用法是作为"弃元"占位符,表示不关心的值:
csharp
// 忽略不需要的返回值
_ = SomeMethod(); // 忽略返回值
// 元组解构时忽略某些值
var (name, _, age) = GetPerson(); // 只关心name和age
// out参数忽略
if (int.TryParse("123", out _))
{
Console.WriteLine("解析成功");
}
// 模式匹配中忽略
if (obj is _) // 匹配任何非null值
{
// ...
}
// switch表达式忽略
var result = input switch
{
_ => "default" // 默认情况
};
2. Lambda表达式参数(C# 9.0+)
表示忽略的lambda参数:
csharp
// 多个下划线表示多个被忽略的参数
Func<int, int, int> func = (_, _) => 42;
// 事件处理中忽略sender和args
button.Click += (_, _) => Console.WriteLine("Clicked");
3. 数字分隔符(C# 7.0+)
提高数字可读性:
csharp
int million = 1_000_000;
long creditCard = 1234_5678_9012_3456;
double pi = 3.141_592_653_589_793;
4. 合法的标识符名称
可用作变量名(但不推荐):
csharp
private string _field; // 私有字段(常见约定)
// 作为临时变量(已不推荐,因为可能和弃元冲突)
var _ = 5; // 现在这会被视为弃元
5. 交互式环境中的特殊变量
在C#交互式窗口(REPL)中,_ 存储上一次操作的结果:
csharp
> 1 + 2
3
> _ * 2 // _ = 3
6
重要注意事项
-
作用域规则 :弃元
_在作用域内可重复使用csharp
(var x, _) = GetData1(); // 第一个弃元 (var y, _) = GetData2(); // 第二个弃元,不冲突 -
避免与变量名冲突 :如果已存在名为
_的变量,则不能同时用作弃元csharp
var _ = 5; // 声明变量 _ = SomeMethod(); // 错误!_ 已被用作变量 -
类型推断:弃元可以是任何类型
csharp
_ = 10; // int _ = "hello"; // string _ = new object(); // object
使用建议
csharp
// ✅ 推荐:明确使用弃元
public void ProcessData()
{
var (success, _, error) = TryGetData();
if (success)
{
// 只使用success和error
}
}
// ✅ 推荐:忽略不需要的参数
button.Click += (_, _) => DoSomething();
// ❌ 不推荐:使用 _ 作为变量名(容易混淆)
var _ = Calculate(); // 可能被误认为是弃元
弃元语法主要目的是提高代码的可读性和性能(避免分配不需要的变量),是现代C#中处理"不需要的值"的推荐方式。
1. 弃元(Discard)的示例
csharp
using System;
class Program
{
static (string, int, string) GetPersonInfo()
{
return ("张三", 25, "北京");
}
static void Main()
{
// 示例1:元组解构时忽略某些值
Console.WriteLine("=== 示例1:元组解构 ===");
var (name, _, city) = GetPersonInfo();
Console.WriteLine($"姓名: {name}"); // 张三
Console.WriteLine($"城市: {city}"); // 北京
// 注意:我们没有使用年龄信息,用 _ 忽略了
Console.WriteLine();
// 示例2:忽略out参数
Console.WriteLine("=== 示例2:忽略out参数 ===");
if (int.TryParse("123", out _))
{
Console.WriteLine("解析成功,但我不知道具体值"); // 会执行
}
if (int.TryParse("abc", out _))
{
Console.WriteLine("不会执行");
}
else
{
Console.WriteLine("解析失败,但我不需要错误信息"); // 会执行
}
Console.WriteLine();
// 示例3:忽略方法返回值
Console.WriteLine("=== 示例3:忽略方法返回值 ===");
var count = 0;
_ = count++; // 忽略递增后的返回值
Console.WriteLine($"count现在的值: {count}"); // 1
Console.WriteLine();
// 示例4:switch表达式中的弃元
Console.WriteLine("=== 示例4:switch表达式 ===");
string GetGrade(int score) => score switch
{
>= 90 => "A",
>= 80 => "B",
>= 70 => "C",
>= 60 => "D",
_ => "F" // 默认情况
};
Console.WriteLine($"85分: {GetGrade(85)}"); // B
Console.WriteLine($"55分: {GetGrade(55)}"); // F
Console.WriteLine();
}
}
输出:
text
=== 示例1:元组解构 ===
姓名: 张三
城市: 北京
=== 示例2:忽略out参数 ===
解析成功,但我不知道具体值
解析失败,但我不需要错误信息
=== 示例3:忽略方法返回值 ===
count现在的值: 1
=== 示例4:switch表达式 ===
85分: B
55分: F
2. 实际应用场景对比
csharp
using System;
using System.Collections.Generic;
class Program
{
// 模拟获取数据的复杂方法
static (bool success, string data, string error) TryGetData()
{
return (true, "重要数据", null);
}
static void Main()
{
Console.WriteLine("=== 对比:使用 vs 不使用 _ ===");
// 传统写法:需要声明不用的变量
var result1 = TryGetData();
if (result1.success)
{
Console.WriteLine($"传统:获取到数据: {result1.data}");
// 但 result1.error 变量还存在,虽然没用
}
// 使用弃元:更清晰,不会有不必要的变量
var (success, data, _) = TryGetData();
if (success)
{
Console.WriteLine($"弃元:获取到数据: {data}");
// 没有 error 变量污染作用域
}
Console.WriteLine();
// 示例:处理列表时的弃元
Console.WriteLine("=== 处理列表 ===");
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// 使用弃元表示我们不关心索引
foreach (var (value, _) in numbers.Select((n, index) => (n, index)))
{
Console.Write($"{value} "); // 只输出值:1 2 3 4 5
}
Console.WriteLine("\n");
// 示例:与 null 检查的对比
Console.WriteLine("=== null 检查对比 ===");
object obj = "test";
// 使用弃元进行模式匹配
if (obj is not null)
{
Console.WriteLine("对象不为null");
}
// 与弃元等价
if (obj is _) // 匹配任何非null对象
{
Console.WriteLine("对象不为null(使用弃元)");
}
}
}
3. Lambda表达式中的弃元
csharp
using System;
class Program
{
static void Main()
{
Console.WriteLine("=== Lambda表达式中的弃元 ===");
// 事件处理:传统方式
Button btn1 = new Button();
btn1.Click += (sender, e) =>
{
Console.WriteLine("按钮被点击了!");
// sender 和 e 都声明了但可能没用到
};
// 使用弃元:明确表示我们不关心参数
Button btn2 = new Button();
btn2.Click += (_, _) =>
{
Console.WriteLine("按钮被点击了!但我不关心谁点击的");
};
// 模拟按钮点击
btn2.OnClick();
Console.WriteLine();
// 示例:Func 中的弃元
Func<int, int, int, string> formatFunc = (a, _, c) =>
$"第一个值: {a}, 第三个值: {c}";
Console.WriteLine(formatFunc(10, 20, 30));
// 输出:第一个值: 10, 第三个值: 30
}
}
// 模拟Button类
class Button
{
public event EventHandler Click;
public void OnClick()
{
Click?.Invoke(this, EventArgs.Empty);
}
}
4. 数字分隔符示例
csharp
using System;
class Program
{
static void Main()
{
Console.WriteLine("=== 数字分隔符 ===");
// 更容易阅读的大数字
int population = 1_411_778_724;
long creditCard = 1234_5678_9012_3456L;
double precise = 3.141_592_653_589_793;
Console.WriteLine($"中国人口: {population}");
Console.WriteLine($"信用卡号: {creditCard}");
Console.WriteLine($"圆周率: {precise}");
// 二进制和十六进制也可以使用
int binary = 0b1010_1100_0011_1100;
int hex = 0xFF_AA_BB_CC;
Console.WriteLine($"二进制: {binary}");
Console.WriteLine($"十六进制: 0x{hex:X}");
}
}
主要作用总结:
-
提高可读性:明确告诉读者"这个值我不关心"
-
避免"未使用变量"警告:编译器知道你是故意不用的
-
减少内存分配:对于大对象特别有用
-
清理作用域:避免不必要的变量污染
-
代码更简洁:不需要给不用的变量起名字
关键理解 :_ 就像一个垃圾桶,你把不需要的东西扔进去,然后就不用管它了。编译器会帮你处理掉这些"垃圾",不会让它们影响你的代码逻辑。