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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With