1 equals "Running"? - Melting Integer and String Objects together

By coincidence, I stumbled upon an article in the PS team blog which talks about comparing different object types. It definitely is worth looking at, but let's take it a step further.

Recently, while I was deserializing service objects, I found that "status" was returned as friendly text such as "Running" or "Stopped" BUT when I took a deeper look, it turned out this property returned Int32, numeric values.

So naturally, two questions arose:

  • first, how mad can it get when Int32 suddenly starts outputting text?
  • and second, why did I take a deeper look at this in the first place? (aka, what is the real world relevance?)

WYSINAWYG - What you see is NOT ALWAYS what you get

Usually, when you output objects to the console, you can safely look at the columns, pick some values and use them for future filtering on those properties, just like this:

Get-Service | Where-Object { $_.Status -eq 'Running' }

With deserialized service objects, this suddenly fails which was when I started to scratch my head:

Get-Service | Export-CliXML $home\services.xml
$services = Import-CliXML $home\services.xml
$services
$services
| Where-Object { $_.Status -eq 'Running' }

The last line simply does not return anything although there definitely are running service objects. Crazy.

Guess why? With deserialized service objects, Status returns text that really is numeric:

$services | Foreach-Object { $_.Status.GetType().FullName }
System.Int32
System.Int32
(...)

So, you would have to compare against the numeric value, not the text that was displayed in the Status column inside the console:

$services | Where-Object { $_.Status -eq 4 }

Uh, that is strange... Do I have your attention now?

Override toString() to get Int32-Mutations...

To make an integer appear as something different, all you need to do is override its toString() method. Replace it with whatever you would like to use to display the variable. Here is a sample:

$b = 1
$b
1

I stored a numeric value in a variable called $b, and naturally, when I output it, it shows what it is: a numeric value. No surprises. But wait!

Next, I override its toString() method with my own ScriptMethod:

$b = $b | Add-Member scriptMethod -pass toString -force { "Running" }
$b
Running

Tada! Now, when I output $b, it changes nature and returns whatever I specified in my toString() method. So my Int32 now looks like a string.

"Oh yeah", you may mumble, "so smarty, why did you not use a string in the first place?" Well, because although $b looks like a string, it is still an Integer:

$b -eq 1
True
$b -eq 'Running'
False

In fact, $b now behaves exactly like the deserialized Status property I received back from my initial service objects. With hybrid Integers, you output information as nice & friendly text so you don't have to look up geeky values to understand what them numbers stand for. Still, you can efficiently compare them against numeric values if you need to.

-like to the Rescue

The only problem with this is that you run into problems like I did if you don't know about this masquerade. You *think* it is text but once you compare it with text, the comparison mumbles "nope, it's an integer, so I return $false" - well it does not even mumble this but silently leaves you puzzling.

A somewhat useful workaround for these cases is -like which compares the value of a variable based on its toString() result as Jason pointed out (thanks for correcting me). Have a look:

$b -like 1
False
$b -like 'Running'
True

So this conclusion helped solve my initial problem. By replacing -eq with -like, all worked fine:

Get-Service | Export-CliXML $home\services.xml
$services = Import-CliXML $home\services.xml
$services
$services
| Where-Object { $_.Status -like 'Running' }

Have a great 2010 and keep tuned,
hey and go ahead and try our PowerShellPlus smart PS console if you are at it, Richard and I would appreciate it!

Tobias

 

P.S.

Richard just commented on my post: "VERY geeky, but nice!" So let's boil it down to one sentence: "If -eq does not seem to work, try -like instead."


Posted Jan 07 2010, 10:19 AM by Tobias

Comments

Jason Archer wrote re: 1 equals "Running"? - Melting Integer and String Objects together
on 01-08-2010 1:20 PM

Odd, I get different results:

PS 3> $b = 1

PS 4> $b = $b | Add-Member scriptMethod -pass toString -force { "Running" }

PS 5> $b

Running

PS 6> $b -eq 1

True

PS 7> $b -eq 'Running'

False

PS 8> $b -like 1

False

PS 9> $b -like 'Running'

True

After some more tests, it seems that -like only works off of ToString(), while -eq does normal object comparison.  Good to know.

BTW, Compare-Object also defaults to comparing ToString() results.

Tobias wrote re: 1 equals "Running"? - Melting Integer and String Objects together
on 01-08-2010 3:05 PM

Jason, you are right. I edited the post to take this into account.

Copyright 2012 PowerShell.com. All rights reserved.