views:

1010

answers:

3

Hi, I'm having problems trying to overload the post increment operator in C#. Using integers we get the following results.

int n;

n = 1;
Console.WriteLine(n++); // 1
Console.WriteLine(n); // 2

n = 1;
Console.WriteLine(++n); // 2
Console.WriteLine(n); // 2

But, when I try it using classes, it looks like the objects are exchanged.

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

Debugging the application, the operator overloaded method, returns the new object with the old value (10) and the object that has been passed by reference, has the new value (11), but finally the objects are exchanged. Why is this happening?

+6  A: 

My first thought was to point out that the normal semantics of ++ are in-place modification. If you want to mimic that you'd write:

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

and not create a new object.

But then I realized that you were trying to mimic the post increment.

So my second thought is "don't do that" -- the semantics don't map well at all onto objects, since the value being "used" is really a mutable storage location. But nobody likes to be told "don't do that" by a random stranger so I'll let Microsoft tell you not to do it. And I fear their word is final on such matters.

P.S. As to why it's doing what it does, you're really overriding the preincrement operator, and then using it as if it were the postincrement operator.

MarkusQ
A: 

You should always return the changed value. C# uses this as the new value, and returns the old or new value as appropriate for the operator.

Ignacio Vazquez-Abrams
+4  A: 

The key is in understanding how the line Account b = a++; works. Given how your code is written, this line is the equivalent of this:

Account b = a;
a++;

And that is the order it will execute in. The assignment effectively(1) happens before the increment. So, the first effect of this line is that a and b both refer to the original object a.

Now the ++ portion will be evaluated. Inside of the operator method, we increment the Balance of the original object. At this point a and b are both pointing at the original, with a Balance of 11, and b will continue to do so.

However, you've created a new object inside the operator method and returned it as the output of the operator. a will now be updated to point at the newly created object.

So, a now points to a new object, while b continues to point to the original. That's why the WriteLine output appears swapped.

As @MarkusQ pointed out, the ++ operator is meant to do in-place modification. By generating a new object, you're breaking that assumption. Operator overloading on objects is a tricky subject, and this is an excellent example of why it's better avoided in most cases.


1 - Just for accuracy's sake, the assignment does not actually happen before the increment when dealing with operators on objects, but the end result is the same in this case. Actually, the original object reference is copied, the operation is carried out on the original, and then the copied reference is assigned to the left-hand variable. It's just easier to explain if you pretend that assignment happens first.

What's really happening is that this:

Account b = a++;

results in this, due to how the ++ operator works on objects:

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
Jeromy Irvine
Good explanation (I don't know for sure if it's right but it makes sense :-). No, seriously, this is good. +1.
paxdiablo