在C#里,&和&&虽然都包含“与”的语义,但实际行为和适用场景差别很大。&既是位运算符也是非短路逻辑运算符,而&&仅仅是短路逻辑运算符。理解它们的底层执行方式,能帮你在写条件判断和位运算时少踩坑。

一、运算符的基本定义
C#中的&&被称为“条件逻辑与”或“短路与”,它只能用于布尔表达式,且具备短路特性。也就是说,如果第一个操作数为false,第二个操作数根本不会被执行。这种机制不仅能提升性能,还能避免一些危险的副作用。
相比之下,&有两种身份。当操作数是布尔类型时,它是“非短路逻辑与”,两边表达式无论结果如何都会被求值;当操作数是整型(如int、byte、long)时,它表示“按位与”,对每一个二进制位执行与运算。很多初学者误以为&只是&&的“全量版”,其实在位运算场景下二者完全不等价。
二、短路与非短路的逻辑差异
我们通过一段有问题的代码来看非短路带来的风险。下面这段代码如果使用&,会在name为null时直接抛出NullReferenceException。
string name = null;
// 使用非短路&,右侧也会执行,导致空引用异常
if (name != null & name.Length > 3)
{
Console.WriteLine(name);
}
// 使用短路&&,左侧为假时右侧不执行,安全
if (name != null && name.Length > 3)
{
Console.WriteLine(name);
}
从上面例子可以看出,在布尔条件判断中,&&能有效防止右侧访问空对象、调用可能异常的方法。而&会强制计算所有条件,这在需要副作用(比如无论真假都要执行某个计数方法)时才考虑使用。
在IL层面,&&会被编译为分支跳转指令,当第一个值为false时直接跳到终点;&则按顺序压入两个操作数再执行and指令。这种差异在高频循环中会被放大,短路版本明显更省CPU周期。
三、位与运算的正确用法
当操作数为整数时,&执行的是按位与。每一位上,只有两个操作数对应位都为1,结果位才为1。它常用于权限掩码、状态标志提取等场景。
int read = 1; // 0001
int write = 2; // 0010
int exec = 4; // 0100
int permission = read | write; // 0011,拥有读和写
// 判断是否有写权限
if ((permission & write) == write)
{
Console.WriteLine("有写权限");
}
// 去掉读权限
permission = permission & ~read;
Console.WriteLine(permission); // 输出2
在上面的权限系统中,我们用&来“遮掩”不需要的位,从而检测或清除特定标志。这种用法&&无法替代,因为&&要求操作数是bool,不能直接对int进行逻辑与。
需要特别注意的是,如果你错误地把两个整数用&&连接,编译器会直接报错,因为不存在从int到bool的隐式转换。这也从语言层面防止了误用。
四、性能与可读性对比
在纯布尔场景下,短路&&几乎总是优于&。它不仅少算表达式,还向阅读代码的人传递了“右侧依赖左侧安全性”的意图。下表总结了主要区别:
| 运算符 | 是否短路 | 适用类型 | 典型用途 |
|---|---|---|---|
| && | 是 | bool | 条件判断、防空检查 |
| &(逻辑) | 否 | bool | 需要两侧都执行的特殊逻辑 |
| &(位) | 不适用 | 整型 | 掩码、位标志、底层计算 |
从维护角度讲,在if语句里看到&,其他开发者会迟疑是否故意要触发副作用;而&&则一目了然是安全判断。因此,除非你有明确理由,否则布尔判断统一用&&。
五、常见误区与避坑建议
有人觉得&比&&“更严谨”,因为两边都检查。其实在绝大多数业务代码里,这种严谨反而引入bug。比如调用一个可能返回null的方法,又紧接着访问其属性,用&就会在null时崩溃。
另一个误区是在异步条件里混用。例如if (await GetFlagAsync() & value > 0),这里虽然编译能过,但await右侧依然会执行,可能发起多余IO。改为&&才能确保前面为false时后面不被触发。写代码时,先问自己“右侧是否依赖左侧结果”,依赖就用短路,做位运算就用&。