C#中单个下划线的语法与用途详解

在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

重要注意事项

  1. 作用域规则 :弃元 _ 在作用域内可重复使用

    csharp

    复制代码
    (var x, _) = GetData1();  // 第一个弃元
    (var y, _) = GetData2();  // 第二个弃元,不冲突
  2. 避免与变量名冲突 :如果已存在名为 _ 的变量,则不能同时用作弃元

    csharp

    复制代码
    var _ = 5;          // 声明变量
    _ = SomeMethod();   // 错误!_ 已被用作变量
  3. 类型推断:弃元可以是任何类型

    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}");
    }
}

主要作用总结:

  1. 提高可读性:明确告诉读者"这个值我不关心"

  2. 避免"未使用变量"警告:编译器知道你是故意不用的

  3. 减少内存分配:对于大对象特别有用

  4. 清理作用域:避免不必要的变量污染

  5. 代码更简洁:不需要给不用的变量起名字

关键理解_ 就像一个垃圾桶,你把不需要的东西扔进去,然后就不用管它了。编译器会帮你处理掉这些"垃圾",不会让它们影响你的代码逻辑。

相关推荐
宇珩前端踩坑日记2 小时前
怎么让 Vue DevTools 用 Trae 打开源码
前端·trae
小徐不会敲代码~2 小时前
Vue3 学习 6
开发语言·前端·vue.js·学习
C_心欲无痕2 小时前
react - useState更新机制(直接更新和函数式更新)
前端·javascript·react.js
GDAL2 小时前
Tailwind CSS 菜单实现全面讲解教程(基于书签篮网站场景)
前端·css·菜单
m5655bj2 小时前
如何通过 C# 实现 PDF 页面裁剪
前端·pdf·c#
这是个栗子2 小时前
前端开发中的常用工具函数(持续更新中...)
前端·javascript·算法
STARBLOCKSHADOW2 小时前
【C#】VS中打包C#桌面软件为exe文件
c#·vs·打包·桌面程序
zhangsansecond2 小时前
vs创建 基于ASP.NET Framework 的 SOAP 协议 Web 服务,https无法访问
前端·https·asp.net
Reese_Cool2 小时前
一篇文章梳理 HTML + CSS 核心知识(含响应式与 Sass)
前端·css·html