运算符重载是类型契约而非语法糖;c#禁止重载+=、??=、.、[]、?:、is、as、new等,可重载+、-、==等但需成对且同步实现equals/gethashcode/iequatable,否则引发行为不一致。

运算符重载不是语法糖,而是类型契约的一部分;用错会导致 == 和 .Equals() 行为不一致、序列化异常、LINQ 查询失效等隐性问题。
哪些运算符能重载,哪些根本不能碰
C# 明确禁止重载的运算符包括:+=、-=、??=、??、.、[](数组索引)、?:、is、as、new、sizeof、typeof、default。这些要么由编译器硬编码处理,要么语义固定不可覆盖。
可安全重载的常见类型:
- 一元:
+、-、!、~、++、--、true、false - 二元:
+、-、*、/、%、&、|、^、、<code>>> - 相等/比较:
==、!=、、<code>>、、<code>>=(但注意:重载==必须同时重载!=;重载就得配齐 <code>>、、<code>>=)
[] 看起来像运算符,但它实际是索引器(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);</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/shouce/1510" title="C函数速查手册(CHM版)"><img
src="https://img.php.cn/upload/manual/000/000/001/5d6de31fedca2993.png" alt="C函数速查手册(CHM版)" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/shouce/1510" title="C函数速查手册(CHM版)" class="overflowclass">C函数速查手册(CHM版)</a>
<p class="overflowclass">C函数速查手册(CHM版)</p>
</div>
<a rel="nofollow" href="/xiazai/shouce/1510" title="C函数速查手册(CHM版)" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><p>public override bool Equals(object? obj) => obj is Box b && Equals(b);</p><p>public bool Equals(Box? other) =>
other is not null && length == other.length && breadth == other.breadth && height == other.height;</p><p>public override int GetHashCode() => HashCode.Combine(length, breadth, height);
</p>
为什么 operator + 返回新实例,而不是修改 this
这是值语义的基本约定。C# 中所有可重载的算术运算符(+、-、* 等)都要求返回新对象,不能就地修改参数——因为参数是传值或传引用的副本,修改它们对调用方不可见,且违背运算符的纯函数直觉。
典型错误写法:
// ❌ 错误:this 不可变,且修改参数 b 不影响外部
public static Box operator +(Box a, Box b) {
a.length += b.length; // 修改了入参 a 的字段?没用,a 是副本
return a;
}
正确做法永远是 new 一个新实例,或调用构造函数:
public static Box operator +(Box a, Box b) =>
new Box { length = a.length + b.length, breadth = a.breadth + b.breadth, height = a.height + b.height };
如果你真需要“就地加”,应提供明确命名的方法,如 MergeInPlace(Box other),而不是重载 +。
自定义类型参与泛型约束或 LINQ 时的隐性陷阱
即使你完整实现了 ==、Equals、GetHashCode,如果没实现 IEqualityComparer<t></t> 或没标记 [Equatable](C# 12+),某些泛型集合(如 Dictionary<box string></box>)仍可能回退到引用比较,尤其在跨 assembly 或 AOT 编译场景下。
关键检查点:
- 确认你的类型是否实现了
IEquatable<t></t>(推荐显式实现,比object.Equals更高效) - 若用于
GroupBy、Distinct等 LINQ 操作,传入自定义IEqualityComparer<box></box>比依赖重载更可控 - 结构体(
struct)重载==后,务必同时实现IEquatable<t></t>,否则装箱后Equals()可能走慢路径
复杂点在于:运算符重载本身不自动触发接口实现,它们是平行机制;你得手动补全这一层契约,否则运行时行为会因上下文不同而分裂。










