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

来源:互联网 发布:淘宝关键词采集器 编辑:程序博客网 时间:2024/05/21 19:42

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 上定义自增运算。

讨论完毕。
原创粉丝点击