Monday, December 12, 2011

Using PowerShell’s ++ (and–-) operator–Take Care

I got a great question the other week in a PowerShell class regarding the ++ operator. The delegated to know if there was any difference between $variable++ and ++$variable. In PowerShell, the operator ++ says, effect to add one to the variable it’s attached to.  So specifying $loopcounter++ as a statement on it’s own (e.g. inside a loop would just add one to the loop counter variable. 

I don’t use the ++ operator a lot – When I was first learning programming, the languages we had (assembler, Cobol, RPG, Fortran and Algol) never had these operators. So incrementing loop counters freestanding is about all I ever do. But I started playing and found some interesting things about ++!

First, looking at my most common use case, $variable++ and ++$variable seem to be the same as this code illustrates:

$lc=0;1..10 | foreach {$lc++};"Loop counter: $lc"
$lc=0;1..10 | foreach {++$lc};"Loop counter: $lc"

produces

Loop counter: 10
Loop counter: 10

 

So at that level the formats are equivalent in function.  And for the most part, the ++ operator used before or after the variable name produces similar results. But not in all cases. Consider the following code fragment:

“Case 1”;$lc=0;1..5 | foreach {"{0}" -f $lc++};"Loop counter: $lc"
”Case 2”;$lc=0;1..5 | foreach {"{0}" -f ++$lc};"Loop counter: $lc"

These two code fragments do not produce the same output. In the first case, the value of LC is formatted into the string, THEN it’s incremented, whereas in the later case, the incrementing occurs before the formatting. Thus the output looks like this:

Case 1
0
1
2
3
4
Loop counter: 5
Case 2
1
2
3
4
5
Loop counter: 5

So as you can see ++$variable is not always the same as $variable++.  Another curiosity of PowerShell that you just have to know!

[Later]

Of course, if you know C# or some other newer languages ++/-- may be old hat to you. And thanks for the comments that came from this post!

6 comments:

Unknown said...

That's not just a PowerShell curiousity...it's how ++ and -- work in many languages. If you use them before the variable, the increment/decrement happens and then the new value is returned. If you use them after the variable, the current value is returned, and then the increment/decrement happens.

Jason Turnage said...

That's exactly how C/C++ and related languages have had ++ implemented as well for a couple decades or more now, I'm sure PowerShell's designers/thinkers/implementers were just conforming to that spec.

jv said...

No need to take care once you understand the history of the operators.

They were first used extensively in 'C" lqnguage. The pre and post use have a very specific and desired effect that we use all of the time.

Try the following and you will immediately see the difference.

$lc=0;1..10 | foreach {"Loop counter:" + $lc++ }

$lc=0;1..10 | foreach {"Loop counter:" + ++$lc }


Look closely at the output. If you only print teh counter after the loop has finished it will always appear to be 10 but outside the loop we have incremented one extra time with post increment. That is because post-increment increments teh counter AFTER the couter has been printed inside the loop. IN most cases we want this behavior.

for($i-0;$i -lt 10; $i++){....}

With ++$lc we increment before we print and the ending counter is always the same as the last one printed.

Here is one explanation of the operators: Pre-Post OPerators

They are very useful once you understand how they work.

Conrad Braam said...

The only gotcha is, that $variable++ does not rvalue.
in C/C++ the pre and post operators actually return a right-hand side value, which make this kind of logic possible
if ( $($variable++) -eq 42) {'We hit 42'}
will never happen, since the pre and post operators don't actually 'return' anything in powershell 4 you cannot test the variable...

Conrad Braam said...

Agree, the c/C++ implementation is already well understood but foudn a curious side-effect
I found this particularly insidious behaviour around the operator not returning an rvalue (right-hand side evaluation) in a way you might expect it to do.
basically... the easiest way to show this is in how
$lc=0;1..10 | foreach {"Loop counter:" + ++$lc }
is completely different to
$lc=0;1..10 | foreach {"Loop counter:" + $(++$lc) } # in case i wanted to write some other manipulations here, the parse gets all smart on me because i scoped the value in a $()
which prints this:
Loop counter:
Loop counter:
Loop counter:
Loop counter:
Loop counter:
Loop counter:
Loop counter:
Loop counter:
Loop counter:
Loop counter:
And it's because the parser is doing something unusual if you assume you get an rvalue. I can see why the parser wont generate an rvalue if you wrote this code
$lc++
Standing all on it's own you would not want to generate an rvalue at all, since that would join the pipeline unexpectedly, something the C compiler knows to gobble up at parse-time because there is no lvalue container.
I found a way to get it to keep the C/C++ behaviour in a one-liner though
$lc=0;1..10 | foreach {"Loop counter:" + $([int]++$lc) }

NeoDog said...

Awesome comment, Conrad Braam. The observation that $(++$i) essentially yields $null, and the discovery of the [int] workaround, are both crucial, and somewhat bizarre.

So this works:

"{0}: hello" -f $i++

But this does not:

"$($i++): hello"

But then this does:

"$([int]$i++): hello"