C# 运算符

运算符是程序里"做计算"的语法,基础但有几个 C# 特有的现代特性值得专门讲——空合并 ??、空条件 ?.、模式匹配。这一篇带你一次过完。

一、算术运算符

int a = 10, b = 3;

Console.WriteLine(a + b);   // 13
Console.WriteLine(a - b);   // 7
Console.WriteLine(a * b);   // 30
Console.WriteLine(a / b);   // 3(整数除法,舍去小数)
Console.WriteLine(a % b);   // 1(取余)

// 注意:整数 / 整数 = 整数(舍去小数)
Console.WriteLine(7 / 2);       // 3
// 要小数结果,至少一个操作数是浮点
Console.WriteLine(7.0 / 2);     // 3.5
Console.WriteLine(7 / 2.0);     // 3.5
Console.WriteLine((double)7 / 2);  // 3.5

// 自增自减
int i = 5;
Console.WriteLine(i++);  // 5(先用后加)
Console.WriteLine(i);    // 6
Console.WriteLine(++i);  // 7(先加后用)

几个常见坑:

二、关系运算符

比较两个值,结果为 bool。C# 的字符串相等比较按内容(string 重写了 ==),这点和 Java 不同:

int a = 10, b = 20;

Console.WriteLine(a > b);    // False
Console.WriteLine(a < b);    // True
Console.WriteLine(a >= 10);  // True
Console.WriteLine(a <= 5);   // False
Console.WriteLine(a == 10);  // True
Console.WriteLine(a != 10);  // False

// 字符串相等:== 比较内容(不是引用)
string s1 = "hello";
string s2 = "hello";
Console.WriteLine(s1 == s2);   // True(重写了 ==)

// 通用相等比较用 Equals
Console.WriteLine(s1.Equals(s2));   // True

注意浮点比较要小心精度。判断两个 double 相等不要直接 ==,而是用 Math.Abs(a - b) < 1e-9

三、逻辑运算符与位运算

bool a = true, b = false;

Console.WriteLine(a && b);   // False(逻辑与)
Console.WriteLine(a || b);   // True(逻辑或)
Console.WriteLine(!a);       // False(逻辑非)

// 短路特性:&& 第一个 false 时第二个不计算
bool ok = false && ExpensiveCheck();   // 不调 ExpensiveCheck

// 位运算(整数)
int x = 0b1100;        // 12
int y = 0b1010;        // 10
Console.WriteLine(x & y);   // 8(0b1000 与)
Console.WriteLine(x | y);   // 14(0b1110 或)
Console.WriteLine(x ^ y);   // 6(0b0110 异或)
Console.WriteLine(~x);      // -13(取反)
Console.WriteLine(x << 2);  // 48(左移 2 位 = 乘 4)
Console.WriteLine(x >> 1);  // 6(右移 1 位 = 除 2)

&&||短路特性:第一个能确定结果时第二个不计算。这点很重要,比如 obj != null && obj.IsValid 必须先判空,短路保证不会 NullReferenceException。

位运算对整数按二进制操作。Flags 枚举、位掩码、加密算法常用。日常业务开发用得少,做底层时频繁。

四、赋值与复合赋值

int a = 10;
a += 5;    // a = a + 5 = 15
a -= 3;    // a = 12
a *= 2;    // a = 24
a /= 4;    // a = 6
a %= 4;    // a = 2
a &= 0xFF; // 位与赋值
a |= 0x10; // 位或赋值
a <<= 2;   // 左移赋值

// C# 8+ 复合空合并
string name = null;
name ??= "默认名";   // 等价 name = name ?? "默认名"
Console.WriteLine(name);   // "默认名"

五、三元运算符

三元运算符 ? : 是简化版 if-else:

int age = 20;
string type = age >= 18 ? "成人" : "未成年";
// "成人"

// 等价于
string type2;
if (age >= 18)
    type2 = "成人";
else
    type2 = "未成年";

三元运算符简洁但别嵌套太深,可读性会下降。一个三元够用,两层嵌套就改用 if-else 或 switch 表达式。

六、空合并运算符 ??(C# 特色)

这是 C# 处理 null 的大杀器。原理:左边为 null 时返回右边,否则返回左边:

string name = null;

// ?? 空合并:null 时返回默认值
string display = name ?? "匿名";   // "匿名"

// 链式空合并
string nickname = null;
string username = null;
string show = nickname ?? username ?? "游客";   // "游客"

// ?. 空条件运算符:null 时不调用方法,返回 null
int? length = name?.Length;   // null(不抛异常)

// 组合使用
int safeLength = name?.Length ?? 0;   // 0

// 老写法(等价但啰嗦)
int oldLength;
if (name != null)
    oldLength = name.Length;
else
    oldLength = 0;

实际场景极多——API 默认值、配置项兜底、用户输入处理。配合空条件 ?. 可以写出非常干净的 null 安全代码,告别冗长的 if 判空。

七、空条件运算符 ?.

对象为 null 时不抛异常,直接返回 null:

// ❌ 容易 NullReferenceException
// int length = name.Length;

// ✅ 安全写法
int? length = name?.Length;     // null 或具体长度

// 链式
int? cityLength = user?.Address?.City?.Length;
// 中间任一为 null,结果为 null

// 配合 ??
int safeLen = user?.Address?.City?.Length ?? 0;

// 调用事件(线程安全)
handler?.Invoke(this, args);

事件触发 handler?.Invoke() 是经典用法,多线程下避免"订阅者刚取消订阅"的崩溃。

八、is 运算符与模式匹配

C# 7+ 引入了强大的模式匹配,远比传统 is 强:

object obj = "hello";

// is 类型检查
if (obj is string)
{
    string s = (string)obj;
    Console.WriteLine(s.Length);
}

// is 模式匹配 + 变量声明(C# 7+)
if (obj is string s)
{
    Console.WriteLine(s.Length);   // 5
}

// switch 表达式(C# 8+):基于模式
int score = 78;
string grade = score switch
{
    >= 90 => "A",
    >= 80 => "B",
    >= 60 => "C",
    _ => "D"
};
// "C"

// 关系模式 + when 守卫
string describe(int n) => n switch
{
    < 0 => "负数",
    0 => "零",
    > 0 and < 10 => "个位正数",
    >= 10 => "多位正数"
};

模式匹配是现代 C# 的杀手锏。switch 表达式 + 关系模式 + when 守卫,让复杂分支变得极简。功能上和 if-else 等价,但可读性和表达力强得多。

九、typeof 与运算符优先级

// typeof:获取类型的 Type 对象(反射)
Type t = typeof(string);
Console.WriteLine(t.FullName);   // System.String

// typeof vs GetType(运行时类型)
string s = "hello";
Type t1 = s.GetType();          // System.String(运行时)
Type t2 = typeof(string);       // System.String(编译时)

// sizeof:获取值类型大小(仅 unsafe 上下文外有限类型可用)
int size = sizeof(int);          // 4

// nameof:获取变量/类型/成员名字(重构友好)
throw new ArgumentException(nameof(param));   // "param"

运算符优先级和数学一致:乘除 > 加减 > 比较 > 逻辑。复杂表达式建议加括号明确意图,不要让人记优先级表。

十、checked 与 unchecked

默认整数运算溢出会"环绕"(不报错),用 checked 可以让溢出抛 OverflowException:

int max = int.MaxValue;   // 2147483647

// 默认 unchecked:溢出环绕
int wrap = max + 1;
Console.WriteLine(wrap);    // -2147483648(负数,危险!)

// checked:抛异常
try
{
    checked
    {
        int bad = max + 1;   // 抛 OverflowException
    }
}
catch (OverflowException)
{
    Console.WriteLine("溢出了");
}

财务计算、安全相关代码应启用 checked。项目级可在 csproj 配置 <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>

小结

算术、关系、逻辑是基础,和 C/Java 没大差别。C# 的特色运算符是空合并 ??、空条件 ?.、模式匹配 is/switch——学会这三个,你写的 C# 代码立刻"地道"很多。下一篇我们看控制流:if、switch、循环。

← 上一篇 C# 数据类型

下一篇 C# 条件与循环

✈️💬