I’ll bet a hundred bucks that any entry level C++ interview or exam will somehow drift into questions about the pre and post increment operators. It’s almost become a canonical, rite of passage sort of thing.

Now using the operators is one thing, overloading them for your own types is another. In C++, you write something like

class X {
    int val;

    X() : val(0){ }

    X operator++() { val++; return *this; }
    X operator++(int) { X pX = *this; val++; return pX; };

    int Value() { return val; }

Fairly straightforward stuff – C++ uses the int overload to distinguish between pre and post, and the post increment overload copies itself before incrementing and returns the copy.

Now let’s see how to do this in C#.

class X
    public int Value { get; set; }

    public static X operator ++(X p)
        int x = p.Value;
        return new X() { Value = x + 1 };

No, there’s no separate overload for the other one – the same method works for both pre and post increment operations. The compiler does the work of generating code that exhibits correct pre and post increment behavior. For code like

X x = new X();
X y = x++;

it generates

X x = new X();
X y = x;
x = X.op_Increment(x);

and for code like

X x = new X();
X y = ++x;

it generates

X x = new X();
X y = x = X.op_Increment(x);

Nifty, eh?

But wait, did you notice the difference between the C++ and C# overload implementation? The C# overload does not modify the original at all and always returns a copy, whereas the C++ code always modifies the current instance and returns a copy only for the post increment overload. Looking at the generated code, it should be easy to understand why – the compiler can’t do its magic if the overload tinkers with the original instance.

Did you notice that X is a reference type?

X x = newX();
Xy = x;


Given that x and y are referring to the same object after executing the second line, shouldn’t the increment be visible from y as well? It doesn’t happen though, because the overload returns a new instance that then gets assigned to x, leaving y referring to the “old” unmodified instance.

All this confusion will not arise if X is a value type. The assignment of x to y will create a copy, so there’s no possibility of changes in x reflecting in y. And modifying the passed instance from within the operator overload will have no effect on the original instance either.

Moral of the story : watch out when overloading operators on reference types.

Leave a Reply

Your email address will not be published. Required fields are marked *