【问题标题】:Post-increment Operator Overloading后增量运算符重载
【发布时间】:2010-10-14 16:51:39
【问题描述】:

我在尝试重载 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),但最终对象被交换。为什么会这样?

【问题讨论】:

标签: c# operator-overloading


【解决方案1】:

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

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

而不是创建一个新对象。

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

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

附:至于 为什么 它正在做它所做的事情,你实际上是在覆盖前增量运算符,然后使用它就好像它是后增量运算符。

【讨论】:

  • 页面不公开,也不存在 :-) 只是我的运气...你还记得标题吗?或者要寻找什么?
【解决方案2】:

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

Account b = a;
a++;

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

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

但是,您在运算符方法中创建了一个新对象,并将其作为运算符的输出返回。 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

【讨论】:

  • 很好的解释(我不确定它是否正确,但它是有道理的:-)。不,说真的,这很好。 +1。
  • 我认为在操作符方法体内创建一个新对象是。只更改新对象,不要改变您获得的原始对象(称为a)。这样,它将像结构一样工作。然后,C# 编译器将正确执行分配,无论是 pre 还是 post。
  • 我认为你的第一个代码块应该是b = a; ++a;(使用后增量)否则你有一个无限递归的解释。
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-04-13
    • 2011-12-21
    • 2010-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-30
    相关资源
    最近更新 更多