C# postfix and prefix increment/decrement overload

2019-01-20 14:36发布

Most sources says that overloading ++ and -- operators in c# results overloading both, postfix and prefix at once. But it looks like their behaviour is still different.

class Counter
{
    public Counter(int v = 0)
    {
        this.v = v;
    }
    public Counter(Counter c)
    {
        v = c.v;
    }
    public int GetValue() { return v; }
    public static Counter operator ++(Counter c)
    {
        c.v++;
        return new Counter(c);
    }
    private int v;
}


class Program
{
    public static void Main()
    {
        Counter c1 = new Counter(1);

        Counter c2 = c1++;

        Counter c3 = ++c1;

        c3++;

        System.Console.WriteLine("c1 = {0}", c1.GetValue());
        System.Console.WriteLine("c2 = {0}", c2.GetValue());
        System.Console.WriteLine("c3 = {0}", c3.GetValue());
    }
}

Wonderfully, although overloaded operator ++ returns copy of the original class, in this example c1 and c3 becomes references to the same object, while c2 points to different one (c1=4, c2=2, c3=4 here). Changing Counter c3 = ++c1; to Counter c3 = c1++; outputs c1=3, c2=2, c3=4.

So, what is the exact difference between postfix and prefix increment/decrement and how it affects overloading? Are these operators acts same way for classes and for primitive types?

1条回答
做个烂人
2楼-- · 2019-01-20 15:30

This is the wrong way to implement increment and decrement in C#. You will get crazy results if you do it wrong; you did it wrong, you got crazy results, so the system works. :-)

Coincidentally I wrote an article about this very subject last week:

http://ericlippert.com/2013/09/25/bug-guys-meets-math-from-scratch/

As commenter dtb points out, the correct implementation is:

    public static Counter operator ++(Counter c)
    {
        return new Counter(c.v + 1);
    }

In C# the increment operator must not mutate its argument. Rather it must only compute the incremented value and return it, without producing any side effects. The side effect of mutating the variable will be handled by the compiler.

With this correct implementation your program now goes like this:

    Counter c1 = new Counter(1);

Call the object that c1 refers to right now W. W.v is 1.

    Counter c2 = c1++;

This has the semantics of:

temp = c1
c1 = operator++(c1) // create object X, set X.v to 2
c2 = temp

So c1 now refers to X, and c2 refers to W. W.v is 1 and X.v is 2.

    Counter c3 = ++c1;

This has the semantics of

temp = operator++(c1) // Create object Y, set Y.v to 3
c1 = temp
c3 = temp

So c1 and c3 now both refer to object Y, and Y.v is 3.

    c3++;

This has the semantics of

c3 = operator++(c3) // Create object Z, set Z.v to 4

So when the smoke all clears:

c1.v = 3 (Y)
c2.v = 1 (W)
c3.v = 4 (Z)

and X is orphaned.

This should give exactly the same results as if you'd had c1, c2 and c3 as normal integers.

查看更多
登录 后发表回答