用户定义的类型可以重载预定义的 C# 运算符。 也就是说,当操作数之一或者两个操作数都属于该类型时,该类型可以提供操作的自定义实现。 “可重载运算符”部分显示哪些 C# 运算符可以重载。
使用 operator 关键字声明运算符。 运算符声明必须满足以下规则:
- 它包括一个
public修饰符。 - 一元运算符有一个输入参数。 二进制运算符有两个输入参数。 在每种情况下,至少有一个参数必须具有类型
T或T?,其中T是包含运算符声明的类型。 - 它包括
static修饰符,复合赋值运算符除外,例如+=。 - 增量(
++)和递减(--)运算符可以作为静态方法或实例方法实现。 实例方法运算符是 C# 14 中引入的新功能。
以下示例定义一个简化的结构来表示合理数字。 结构重载一些 算术运算符:
public struct Fraction
{
private int numerator;
private int denominator;
public Fraction(int numerator, int denominator)
{
if (denominator == 0)
{
throw new ArgumentException("Denominator cannot be zero.", nameof(denominator));
}
this.numerator = numerator;
this.denominator = denominator;
}
public static Fraction operator +(Fraction operand) => operand;
public static Fraction operator -(Fraction operand) => new Fraction(-operand.numerator, operand.denominator);
public static Fraction operator +(Fraction left, Fraction right)
=> new Fraction(left.numerator * right.denominator + right.numerator * left.denominator, left.denominator * right.denominator);
public static Fraction operator -(Fraction left, Fraction right)
=> left + (-right);
public static Fraction operator *(Fraction left, Fraction right)
=> new Fraction(left.numerator * right.numerator, left.denominator * right.denominator);
public static Fraction operator /(Fraction left, Fraction right)
{
if (right.numerator == 0)
{
throw new DivideByZeroException();
}
return new Fraction(left.numerator * right.denominator, left.denominator * right.numerator);
}
// Define increment and decrement to add 1/den, rather than 1/1.
public static Fraction operator ++(Fraction operand)
=> new Fraction(operand.numerator + 1, operand.denominator);
public static Fraction operator --(Fraction operand) =>
new Fraction(operand.numerator - 1, operand.denominator);
public override string ToString() => $"{numerator} / {denominator}";
// New operators allowed in C# 14:
public void operator +=(Fraction operand) =>
(numerator, denominator ) =
(
numerator * operand.denominator + operand.numerator * denominator,
denominator * operand.denominator
);
public void operator -=(Fraction operand) =>
(numerator, denominator) =
(
numerator * operand.denominator - operand.numerator * denominator,
denominator * operand.denominator
);
public void operator *=(Fraction operand) =>
(numerator, denominator) =
(
numerator * operand.numerator,
denominator * operand.denominator
);
public void operator /=(Fraction operand)
{
if (operand.numerator == 0)
{
throw new DivideByZeroException();
}
(numerator, denominator) =
(
numerator * operand.denominator,
denominator * operand.numerator
);
}
public void operator ++() => numerator++;
public void operator --() => numerator--;
}
public static class OperatorOverloading
{
public static void Main()
{
var a = new Fraction(5, 4);
var b = new Fraction(1, 2);
Console.WriteLine(-a); // output: -5 / 4
Console.WriteLine(a + b); // output: 14 / 8
Console.WriteLine(a - b); // output: 6 / 8
Console.WriteLine(a * b); // output: 5 / 8
Console.WriteLine(a / b); // output: 10 / 4
}
}
可以通过定义隐式转换从int到Fraction来扩展前面的示例。 然后,重载运算符将支持这两种类型的参数。 也就是说,可以将一个整数添加到一个分数中,得到一个分数结果。
还可以使用 operator 关键字来定义自定义类型转换。 有关详细信息,请参阅用户定义转换运算符。
可重载运算符
下表显示了可重载的运算符:
| 运营商 | 注释 |
|---|---|
+x、-x、!x、~x、++、--、true、false |
必须同时重载 true 和 false 运算符。 |
x + y、、x - yx * y、x / y、x % y、x & y、、x | yx ^ y、、x << y、x >> y、x >>> y |
|
x == y、x != y、x < y、x > y、x <= y、x >= y |
必须按如下方式成对重载:== 和 !=、< 和 >、<= 和 >=。 |
+=、-=、*=、/=、%=、&=、\|=、^=、<<=、>>=、>>>= |
复合赋值运算符可以在 C# 14 及更高版本中重载。 |
复合赋值重载运算符必须遵循以下规则:
- 它必须包含
public修饰符。 - 它不能包含
static修饰符。 - 返回类型必须是
void。 - 声明必须包含一个参数,该参数表示复合赋值右侧。
从C# 14版本开始,递增(++)和递减(--)运算符可以重载为实例成员。 实例运算符可以通过避免创建新实例来提高性能。 实例运算符必须遵循以下规则:
- 它必须包含
public修饰符。 - 它不能包含
static修饰符。 - 返回类型必须是
void。 - 它不能声明任何参数,即使这些参数具有默认值。
不可重载运算符
下表显示了无法重载的运算符:
| 运营商 | 替代方案 |
|---|---|
x && y、x || y |
重载true和false运算符以及&或|运算符。 有关详细信息,请参阅 用户定义的条件逻辑运算符。 |
a[i]、a?[i] |
定义 索引器。 |
(T)x |
定义由强制类型转换表达式执行的自定义类型转换。 有关详细信息,请参阅用户定义转换运算符。 |
^x、、x = yx.y、x?.y、c ? t : f、x ?? y、、 ??= yx..y、x->y、=>、f(x)、as、await、checked、unchecked、default、delegate、is、nameof、newsizeof、stackalloc、switch、typeof、with |
没有。 |
在 C# 14 之前,复合运算符无法重载。 重载相应的二进制运算符会隐式重载相应的复合赋值运算符。
运算符重载解析
重要
本部分适用于 C# 14 及更高版本。 在 C# 14 之前,不允许用户定义的复合赋值运算符和实例递增和递减运算符。
如果在包含 x 的复合赋值表达式中,x «op»= y 被分类为变量,则对于 «op»,实例运算符优于任何静态运算符。 如果未为变量类型«op»=声明重x载运算符或x未分类为变量,则使用静态运算符。
对于后缀运算符 ++,如果未 x 分类为变量 或使用 表达式 x++ ,则忽略该实例 operator++ 。 否则,实例operator ++优先。 例如,
x++; // Instance operator++ preferred.
y = x++; // instance operator++ isn't considered.
此规则的目的是,在 y 被递增x,应将值赋给 。 编译器无法确定引用类型中用户定义实现的具体实现方式。
对于前缀运算符 ++,如果 x 分类为变量, ++x则实例运算符优先于静态一元运算符。
C# 语言规范
有关更多信息,请参阅 C# 语言规范的以下部分:
另请参阅
- C# 运算符和表达式
- 用户定义的转换运算符
- 设计准则 - 运算符重载
- 设计准则 - 相等运算符
- 为什么重载运算符在 C# 中始终是静态的?