operator + 必须是 public static,因为c#运算符重载本质是静态方法调用,编译器直接查找 public static t operator +(t a, t b),不支持实例方法或虚调用。

为什么 operator + 必须是 public static
因为 C# 的运算符重载本质是静态方法调用,编译器在遇到 a + b 时,会直接查找类型定义的 public static T operator +(T a, T b),不走虚方法表,也不支持实例方法。不是「约定」,是语言硬性要求。
常见错误现象:CS0563(二元运算符必须恰好有一个参数为包含类型)、CS0562(一元运算符第一个参数必须是所在类型)——多半是忘了 static 或参数类型写错了。
- 两个操作数中,至少一个必须是当前类或结构体类型
- 返回类型通常和左操作数类型一致(比如
Vector + Vector → Vector),但不是强制;返回int也合法,只是语义混乱 - 不能只重载
+而不配对重载==和!=—— 否则启用Warning CS0660/CS0661,编译器会提醒你逻辑不一致
operator - 重载时要不要处理 null?
结构体(struct)天然不可空,不用考虑;但类(class)必须主动防御 null,否则运行时抛 NullReferenceException,而且这个异常不会出现在运算符签名里,容易漏测。
使用场景:比如 Money 类做减法,用户传进 null 很常见,尤其从 JSON 反序列化失败或数据库字段为空时。
- 推荐在方法体开头加
if (a is null || b is null) throw new ArgumentNullException(...); - 不建议静默返回
null或默认值——这会让调用方误以为计算成功,后续逻辑崩得更晚 - 如果业务允许空语义(如「未知金额无法参与计算」),应改用可空引用类型(
Money?)并显式用??处理,而不是在operator -里吞掉null
重载算术运算符后,+= 和 -= 会自动生效吗?
会。只要定义了 operator +,a += b 就等价于 a = a + b;同理 -= 依赖 operator -。C# 不要求、也不允许单独重载复合赋值运算符。
性能影响:注意这是「重新赋值」,不是原地修改。比如 List<int></int> 自定义类若重载 + 返回新实例,+= 就会产生一次额外分配——和 string 类似,不是 StringBuilder 那种就地追加。
- 如果类型是引用类型且内部状态大(如含数组、缓存),要考虑是否真需要支持
+=;有时显式调用AddRange()更清晰 - 结构体重载时,
+=仍会触发装箱(如果返回值是object等)——但只要返回本类型,就不会装箱 - 别试图在
operator +里做副作用(如修改this字段),因为+=的语义是「计算再赋值」,不是「就地累加」
和内置类型混用时,隐式转换怎么设才不翻车?
比如希望 MyNumber + 5 能通过,就得提供从 int 到 MyNumber 的隐式转换;但滥用隐式转换极易引发意外行为,比如 if (myNum == 0) 突然触发转换再比较,结果不符合直觉。
兼容性影响:.NET 6+ 开启 nullable 上下文后,隐式转换若涉及可空类型,可能被编译器警告(CS8600)。
- 优先用显式转换(
explicit)——比如(int)myNum,把歧义交给调用方决定 - 如果真要隐式,只对无损、无歧义的转换开放:如
long → MyNumber(假设内部用long存储),但禁止double → MyNumber(精度丢失) - 别给
string写隐式转换——解析失败时只能抛异常,而运算符里不该抛异常;该用构造函数或TryParse
最常被忽略的是:重载运算符后,如果没同步更新 Equals、GetHashCode 和 IEquatable<t></t>,集合查找、字典键行为就会出错——这不是语法问题,是逻辑一致性漏洞,调试起来特别绕。











