17

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

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

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

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"
    }
}

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

4

3 回答 3

17

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

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

而不是创建一个新对象。

但后来我意识到你是在试图模仿帖子增量。

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

PS 至于为什么要这样做,您实际上是在覆盖 preincrement 运算符,然后像使用 postincrement 运算符一样使用它。

于 2009-03-21T05:10:58.537 回答
12

关键是要理解这条线是如何Account b = a++;工作的。鉴于您的代码是如何编写的,这一行相当于:

Account b = a;
a++;

这就是它将执行的顺序。有效的分配(1)发生在增量之前。所以,这一行的第一个效果是ab都引用了原始对象a

现在将评估 ++ 部分。在 operator 方法内部,我们增加Balance了原始对象的 。此时ab都指向原来的,aBalance为11,b会继续这样做。

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

因此,a现在指向一个新对象,而b继续指向原来的对象。这就是 WriteLine 输出出现交换的原因。

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


1 -为了准确起见,在处理对象上的运算符时,赋值实际上并没有发生在增量之前,但在这种情况下最终结果是相同的。其实就是复制原始对象引用,对原始对象进行操作,然后将复制的引用赋值给左边的变量。如果您假装首先发生分配,则更容易解释。

真正发生的是:

Account b = a++;

由于 ++ 运算符对对象的工作方式,导致了这一点:

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
于 2009-03-21T05:30:29.473 回答
1

您应该始终返回更改后的值。C# 将此用作新值,并根据运算符返回旧值或新值。

于 2009-03-21T05:18:08.240 回答