关于C#:后增量运算符重载

Post-increment Operator Overloading

我在试图用C_重载后增量运算符时遇到问题。使用整数,我们得到以下结果。

1
2
3
4
5
6
7
8
9
10
11
int n;

n = 10;
Console.WriteLine(n); // 10
Console.WriteLine(n++); // 10
Console.WriteLine(n); // 11

n = 10;
Console.WriteLine(n); // 10
Console.WriteLine(++n); // 11
Console.WriteLine(n); // 11

但是,当我使用类尝试它时,看起来对象是交换的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Account
{
    public int Balance { get; set; }
    public string Name { get; set; }

    public Account(string name, int balance)
    {
        Balance = balance;
        Name = name;
    }

    public override string ToString()
    {
        return Name +"" + Balance.ToString();
    }

    public static Account operator ++(Account a)
    {
        Account b = new Account("operator ++", a.Balance);
        a.Balance += 1;
        return b;
    }

    public static void Main()
    {
        Account a = new Account("original", 10);

        Console.WriteLine(a); //"original 10"

        Account b = a++;

        Console.WriteLine(b); //"original 11", expected"operator ++ 10"
        Console.WriteLine(a); //"operator ++ 10", expected"original 11"
    }
}

调试应用程序时,重载的operator方法会返回旧值(10)的新对象和通过引用传递的对象,并具有新值(11),但最终会交换对象。为什么会这样?


我的第一个想法是指出++的正常语义是就地修改。如果你想模仿你会写的:

1
2
3
4
5
public static Account operator ++(Account a)
{
    a.Balance += 1;
    return a;
}

不创建新对象。

但后来我意识到你在模仿后增量。

所以我的第二个想法是"不要这样做"——语义并没有很好地映射到对象上,因为被"使用"的值实际上是一个可变的存储位置。但是没有人喜欢被一个随机的陌生人告诉"不要这样做",所以我会让微软告诉你不要这样做。我担心他们的话在这些问题上是最终的决定。

另外,关于它为什么要做它所做的,您实际上覆盖了preincrement操作符,然后像使用post increment操作符一样使用它。


关键在于理解Account b = a++;线是如何工作的。考虑到代码是如何编写的,这一行相当于:

1
2
Account b = a;
a++;

这就是它将要执行的命令。分配有效地发生在增量之前(1)。所以,这一行的第一个效果是A和B都引用了原始对象A。

现在将计算++部分。在operator方法内部,我们增加原始对象的Balance。此时A和B都指向原图,Balance为11,B将继续这样做。

但是,您已经在operator方法中创建了一个新对象,并将其作为运算符的输出返回。现在将更新a以指向新创建的对象。

现在a指向一个新对象,而b继续指向原始对象。这就是为什么写行输出出现交换的原因。

正如@markusq所指出的那样,++操作符是用来进行就地修改的。通过生成一个新对象,你就打破了这个假设。对象上的运算符重载是一个棘手的问题,这是一个很好的例子,说明为什么在大多数情况下最好避免它。

1-为了精确起见,在处理对象上的运算符时,赋值实际上不会发生在增量之前,但在这种情况下,最终结果是相同的。实际上,复制原始对象引用,对原始对象执行操作,然后将复制的引用分配给左侧变量。如果你假装作业先发生,解释起来就简单多了。

真正发生的是:

1
Account b = a++;

结果是,由于++运算符如何处理对象:

1
2
3
4
5
6
7
Account copy = a;

Account x = new Account("operator ++", a.Balance);
a.Balance += 1; // original object's Balance is incremented
a = x; // a now points to the new object, copy still points to the original

Account b = copy; // b and copy now point at the same, original, object


您应该始终返回更改的值。C将此值用作新值,并根据操作员的需要返回旧值或新值。