关于 C# 自增运算符(operator ++)的重载

C# 支持用户自定义 structclass 的运算符重载。但是对于自增运算符(operator ++),有一些细节需要留意。特别是如果按照 C++ 自增运算符重载的思路去理解 C# 的自增运算符重载,那会遇到很多问题。

重点提示

1. 不要在 class 上定义自增运算。

2. 在 struct 上定义自增运算函数(operator++)时,必须返回一个比原值大 1 的新 struct

引例

用一个例子引入。比如我们定义如下的一个简单的 struct,来 wrap 一个整数(这样做的目的有多种可能,比如在算术溢出时抛出异常;但这不是本文的重点):

struct MyInteger
{
    public int Value { get; private set; }
    public MyInteger(int value)
    {
        this.Value = value;
    }
}

如果我们希望像使用寻常 int 变量那样使用 MyInteger,比如像下面这样使用:

for (MyInteger i = 0; i < 5; i++)
{
    // ...
}

那该怎么办呢?自然,我们需要重载 < 运算符(及 > 运算符)和 ++ 运算符。本文只探讨如何重载 ++ 运算符的问题。

先看看一种“自然”但错误的方式(这种方式和 C++ 里重载后置++ 运算符的形式非常相似):

// The following code will NOT work as expected.
public static MyInteger operator++(MyInteger x)
{
    MyInteger y = x;
    x.Value++;
    return y;
}

这段代码实际上是行不通的。比如在前一段循环中,i 永远都是零。

分析

要理解其原因,必须看看 C# 是如何处理 ++ 重载的。C# 里的 ++ 运算符重载方式与 C++ 不同:在 C++ 中,前置++ 和后置 ++ 通过不同的函数重载,其行为完全由实现者控制,并且也由实现者来保证其行为符合常规的前置和后置语义。而在 C# 中,++ 运算符的重载函数只有一个,这个函数应该做的事情就是返回一个比参数大 1 的值;而前置和后置的语义是由编译器决定的,实现者自己没法修改。

说了这么多废话,其实需要知道的只是下面两条规则

1. C# 的前置自增(j = ++i)等价于

i = operator++(i);
j = i;

2. C# 的后置自增(j = i++)等价于

j = i;
i = operator++(i);

这两条规则对 structclass 都适用。

尽管看上去很简单,但是这与 C++ 中的自增运算有微妙的区别。可以这么说,在 C# 里不存在“自”增这回事;看上去的“自”增,实际上是令被自增的变量等于一个以该变量为参数的函数的值。

进一步,由于 structclass 的特性,前置和后置 ++ 的特性也不相同:

对于 struct

1. 为了实现前置自增(j=++i)语义,operator++ 函数必须返回 i+1 的值;但其内部对i 的修改并不能产生任何效果(因为 struct 按值传递)。C# 会保证 j 等于自增后的i 值。

2. 为了实现后置自增(j=i++)语义,operator++ 函数必须返回 i+1 的值;但其内部对i 的修改并不能产生任何效果(因为 struct 按值传递)。C# 会保证 j 等于自增前的i值。

对于 class

1. 为了实现前置自增(j=++i)语义,operator++ 函数必须返回等价于 i+1 的对象;这个对象可以是新对象,也可以是自增之后的i 本身;而 j 永远和 i 指向同一个对象。

2. 为了实现后置自增(j=i++)语义,operator++ 函数必须返回等价于 i+1 的新对象,因为j 已经指向了 i 自增前的那个对象,如果 operator++ 仅仅是修改了 i 的值就直接返回 i 的话,那么 j 的值也会变成了自增以后的值了。

总结

综合上述四种情况可以发现,唯一放之四海而皆准的 operator++ 实现方法,就是返回一个新的值(对于 struct)或者对象(对于 class

比如,在最初的例子当中,正确的(或者说恰当的)operator++ 的定义方式是这样的:

// This is the right way to implement ++ on a struct.
public static MyInteger operator++(MyInteger x)
{
    return new MyInteger(x.Value + 1);
}

然而,对于类上定义的自增运算,还有一个问题。考虑下面的代码:

// i, j are objects of the same class.
j = i;
++j;
++j 之后, j 的值自然是 +1 了,那么 i 的值应该不变还是 +1 呢?答案并不显然。如果 operator++ 函数返回了一个新对象,那么 i 的值不变;如果 operator++ 返回了修改后的对象,那么 i 的值会跟着变。而从直观上讲, ++j 应当增加 j 指向的对象呢,还是让 j 另外指向一个比原来大 1 的对象呢?这也没有很自然的答案。因此,最好遵循下面这条规则:

不要在 class 上定义自增运算。而只在 struct 上定义自增运算。

讨论完毕。
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值