用户定义的类型可以重载预定义的 C# 运算符。 也就是说,当操作数之一或者两个操作数都属于该类型时,该类型可以提供操作的自定义实现。 “可重载运算符”部分显示哪些 C# 运算符可以重载。
使用 operator
关键字声明运算符。 运算符声明必须满足以下规则:
- 它包括一个
public
修饰符。 - 一元运算符有一个输入参数。 二进制运算符有两个输入参数。 在每种情况下,至少有一个参数必须具有类型
T
或T?
,其中T
是包含运算符声明的类型。 - 它包括
static
修饰符,复合赋值运算符除外,例如+=
。 - 增量(
++
)和递减(--
)运算符可以作为静态方法或实例方法实现。
以下示例定义一个简化的结构来表示合理数字。 结构重载一些 算术运算符:
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++, operand.denominator);
public static Fraction operator --(Fraction operand) =>
new Fraction(operand.numerator--, 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 - y x * y 、x / y 、x % y 、x & y 、、x | y x ^ 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 = y x.y 、x?.y 、c ? t : f 、x ?? y 、、 ??= y x..y 、x->y 、=> 、f(x) 、as 、await 、checked 、unchecked 、default 、delegate 、is 、nameof 、new sizeof 、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# 中始终是静态的?