运算符重载是类型契约而非语法糖;C#禁止重载+=、-=、??=、.、[]、?:、is、as、new等,可重载+、-、==等但须成对实现且同步重写Equals/GetHashCode/IEquatable,否则引发==与.Equals()行为不一致、LINQ失效、序列化异常等问题。

运算符重载不是语法糖,是类型契约的一部分;用错会导致 == 和 .Equals() 行为不一致、LINQ 查询失效、序列化异常等隐性问题。
哪些运算符能重载,哪些根本不能碰
C# 明确禁止重载的运算符包括:+=、-=、??=、.、[]、?:、is、as、new、sizeof、typeof、default。这些要么由编译器硬编码处理,要么语义固定不可覆盖。
可安全重载的常见类型:
- 一元:
+、-、!、~、++、--、true、false - 二元:
+、-、*、/、%、&、|、^、<<、>> - 相等/比较:
==、!=、<、>、<=、>=(注意:重载==必须同时重载!=;重载<就得配齐>、<=、>=)
[] 看起来像运算符,但它实际是索引器(this[]),必须用 get/set 实现,不是 operator 声明。
重载 == 和 != 时最容易漏掉的三件事
只写 public static bool operator ==(Box a, Box b) 是危险的——它绕过了 object.Equals() 的虚调用链,且默认会触发引用比较逻辑,导致值语义和引用语义混用。
必须同步做以下操作:
- 重载
!=,且实现逻辑必须与==严格互斥(别手抖写反) - 重写
Equals(object? obj)和GetHashCode(),确保与==语义完全一致 - 在
==方法里加空值检查:先判断ReferenceEquals(a, null) || ReferenceEquals(b, null),再进入字段比较,否则null == someBox会抛NullReferenceException
示例骨架:
public static bool operator ==(Box a, Box b) =>
ReferenceEquals(a, b) ||
(!ReferenceEquals(a, null) && !ReferenceEquals(b, null) && a.Equals(b));
<p>public static bool operator !=(Box a, Box b) => !(a == b);运算符重载必须满足的三个硬性条件
不符合任一条件,编译直接报错:
- 必须使用
public static修饰符 - 必须使用
operator关键字 - 至少有一个参数是当前类型(比如
Box类重载+,参数列表里必须含Box)
例如这个写法合法:
public static Box operator +(Box a, Box b) { ... }而这个不合法(两个参数都不是 Box):
public static Box operator +(int x, int y) { ... } // ❌ 编译失败也不合法(缺少 static):
public bool operator ==(Box a, Box b) { ... } // ❌ 编译失败为什么重载了 == 还要重写 Equals 和 GetHashCode
因为 == 是静态方法,不参与虚方法调度;而 Equals 是虚方法,会被 Dictionary<TKey, TValue>、List<T>.Contains()、LINQ 的 Distinct() 等大量框架 API 调用。
如果只改 == 不动 Equals,会出现这种诡异现象:
-
box1 == box2返回true -
box1.Equals(box2)却返回false -
new HashSet<Box> { box1 }.Contains(box2)返回false
更隐蔽的问题是:若没重写 GetHashCode(),两个逻辑相等的对象可能被散列到不同桶里,导致字典查找失败或集合去重失效。
所以,只要重载了 ==,就必须让 Equals 和 GetHashCode 与之保持行为一致 —— 这不是“建议”,是契约义务。


















