C# 运算符重载
可重载的运算符、必须成对定义、以及隐式/显式转换。
Key Points重载 `==` 必须同时重载 `!=`,并重写 `Equals` 和 `GetHashCode`。
基本用法
C#
class Vector
{
public double X { get; }
public double Y { get; }
public Vector(double x, double y) { X = x; Y = y; }
public static Vector operator +(Vector a, Vector b) => new(a.X + b.X, a.Y + b.Y);
public static Vector operator -(Vector a, Vector b) => new(a.X - b.X, a.Y - b.Y);
public static Vector operator *(Vector a, double k) => new(a.X * k, a.Y * k);
// 一元
public static Vector operator -(Vector a) => new(-a.X, -a.Y);
public override string ToString() => $"({X},{Y})";
}
var a = new Vector(1, 2);
var b = new Vector(3, 4);
Console.WriteLine(a + b);
Console.WriteLine(a - b);
Console.WriteLine(a * 2);
Console.WriteLine(-a);Output
(4,6)
(-2,-2)
(2,4)
(-1,-2)比较运算符
C#
class Money : IEquatable<Money>
{
public decimal Amount { get; }
public string Currency { get; }
public Money(decimal amount, string currency)
{
Amount = amount;
Currency = currency;
}
// == 和 != 必须成对
public static bool operator ==(Money? a, Money? b) => Equals(a, b);
public static bool operator !=(Money? a, Money? b) => !Equals(a, b);
public static bool operator >(Money a, Money b) => a.Amount > b.Amount;
public static bool operator <(Money a, Money b) => a.Amount < b.Amount;
// 重载 == 就必须重写 Equals 和 GetHashCode
public bool Equals(Money? other) =>
other is not null && Amount == other.Amount && Currency == other.Currency;
public override bool Equals(object? obj) => Equals(obj as Money);
public override int GetHashCode() => HashCode.Combine(Amount, Currency);
public override string ToString() => $"{Amount} {Currency}";
}
var m1 = new Money(100, "CNY");
var m2 = new Money(100, "CNY");
var m3 = new Money(200, "CNY");
Console.WriteLine(m1 == m2);
Console.WriteLine(m1 != m3);
Console.WriteLine(m3 > m1);Output
True
True
TrueOverloading
== without overriding Equals/GetHashCode triggers warnings (CS0660/CS0661) and makes Dictionary, HashSet and LINQ Distinct disagree. Always change all three together.转换运算符
C#
class Celsius
{
public double Value { get; }
public Celsius(double v) => Value = v;
// 隐式:不会丢信息
public static implicit operator double(Celsius c) => c.Value;
// 显式:可能丢信息,必须强转
public static explicit operator int(Celsius c) => (int)c.Value;
public override string ToString() => $"{Value}°C";
}
var c = new Celsius(36.6);
double d = c; // 隐式,直接赋值
int i = (int)c; // 显式,需要强转
Console.WriteLine(c);
Console.WriteLine(d);
Console.WriteLine(i);Output
36.6°C
36.6
36Use implicit when the conversion is always safe (a wrapper to its raw value) and explicit when precision can be lost. Overusing implicit hides conversions and makes debugging hard.
可重载的运算符
| Overloadable | Not overloadable |
|---|---|
+ - * / % | && ||(需同时重载 true/false) |
== != < > <= >= | . ?. :: |
! ~ ++ -- | = += 等赋值(由运算派生) |
& | ^ << >> | new typeof sizeof |
true false | is as ?: |
[](索引器) | ?? ??= |
该不该重载
- 该 —— 数学/物理类型(
Vector、Matrix、Complex)、金额、日期区间 - 该 —— 运算符的含义直观且无歧义
- 不该 —— 业务实体(
Order + Order是什么意思?) - 不该 —— 有副作用(
a + b改了数据库就太可怕了)
The test is simple: overload when the mathematical intuition holds; otherwise use a named method. money1 + money2 reads naturally; order1 + order2 confuses — call it Merge.
基本用法
C#
class Vector
{
public double X { get; }
public double Y { get; }
public Vector(double x, double y) { X = x; Y = y; }
public static Vector operator +(Vector a, Vector b) => new(a.X + b.X, a.Y + b.Y);
public static Vector operator -(Vector a, Vector b) => new(a.X - b.X, a.Y - b.Y);
public static Vector operator *(Vector a, double k) => new(a.X * k, a.Y * k);
// 一元
public static Vector operator -(Vector a) => new(-a.X, -a.Y);
public override string ToString() => $"({X},{Y})";
}
var a = new Vector(1, 2);
var b = new Vector(3, 4);
Console.WriteLine(a + b);
Console.WriteLine(a - b);
Console.WriteLine(a * 2);
Console.WriteLine(-a);Output
(4,6)
(-2,-2)
(2,4)
(-1,-2)比较运算符
C#
class Money : IEquatable<Money>
{
public decimal Amount { get; }
public string Currency { get; }
public Money(decimal amount, string currency)
{
Amount = amount;
Currency = currency;
}
// == 和 != 必须成对
public static bool operator ==(Money? a, Money? b) => Equals(a, b);
public static bool operator !=(Money? a, Money? b) => !Equals(a, b);
public static bool operator >(Money a, Money b) => a.Amount > b.Amount;
public static bool operator <(Money a, Money b) => a.Amount < b.Amount;
// 重载 == 就必须重写 Equals 和 GetHashCode
public bool Equals(Money? other) =>
other is not null && Amount == other.Amount && Currency == other.Currency;
public override bool Equals(object? obj) => Equals(obj as Money);
public override int GetHashCode() => HashCode.Combine(Amount, Currency);
public override string ToString() => $"{Amount} {Currency}";
}
var m1 = new Money(100, "CNY");
var m2 = new Money(100, "CNY");
var m3 = new Money(200, "CNY");
Console.WriteLine(m1 == m2);
Console.WriteLine(m1 != m3);
Console.WriteLine(m3 > m1);Output
True
True
True只重载
== 不重写 Equals/GetHashCode 会触发编译警告(CS0660/CS0661),而且会让 Dictionary、HashSet、LINQ 的 Distinct 行为不一致。三者必须一起改。转换运算符
C#
class Celsius
{
public double Value { get; }
public Celsius(double v) => Value = v;
// 隐式:不会丢信息
public static implicit operator double(Celsius c) => c.Value;
// 显式:可能丢信息,必须强转
public static explicit operator int(Celsius c) => (int)c.Value;
public override string ToString() => $"{Value}°C";
}
var c = new Celsius(36.6);
double d = c; // 隐式,直接赋值
int i = (int)c; // 显式,需要强转
Console.WriteLine(c);
Console.WriteLine(d);
Console.WriteLine(i);Output
36.6°C
36.6
36implicit 用于「转换总是安全」的场景(如自定义类型转它包装的原始值),explicit 用于可能丢精度的情况。滥用 implicit 会让代码里的类型转换悄无声息,很难排查。
可重载的运算符
| 可重载 | 不可重载 |
|---|---|
+ - * / % | && ||(需同时重载 true/false) |
== != < > <= >= | . ?. :: |
! ~ ++ -- | = += 等赋值(由运算派生) |
& | ^ << >> | new typeof sizeof |
true false | is as ?: |
[](索引器) | ?? ??= |
该不该重载
- 该 —— 数学/物理类型(
Vector、Matrix、Complex)、金额、日期区间 - 该 —— 运算符的含义直观且无歧义
- 不该 —— 业务实体(
Order + Order是什么意思?) - 不该 —— 有副作用(
a + b改了数据库就太可怕了)
判断标准很简单:如果运算符的数学直觉成立,就重载;否则用命名方法。money1 + money2 直观,order1 + order2 让人困惑——后者应该叫 Merge。