Skip to content

Instantly share code, notes, and snippets.

@Jaykul
Last active March 4, 2020 19:50
Show Gist options
  • Save Jaykul/bb6269ee483e1323fd77ef4c7c4cc290 to your computer and use it in GitHub Desktop.
Save Jaykul/bb6269ee483e1323fd77ef4c7c4cc290 to your computer and use it in GitHub Desktop.
PowerShell Casting Oddities

PowerShell's casting can result in some weird results.

Check out User.cs down below. It's a pretty simple class with three constructors: the default (parameterless) one, and one each taking the user name, and id. PowerShell will cast using the constructors of a type, so in this case, you can cast either a string or a number --or a hashtable-- to User.

We can add this type to our PowerShell session using Add-Type:

Add-Type -path User.cs

The most interesting of PowerShell's special casting powers is the hashtable cast. Any class that has a default (parameterless) constructor can be created by casting a hashtable of (some of) it's settable properties:

[user]@{
Id = 1
Name = "Jaykul"
}
Id Name   OAuthUri
-- ----   --------
 1 Jaykul

One of the weirdest examples is this id constructor.

In the case of this particular User class, when we cast a number, like [User]2 it's the same as calling [User]::new(2), but the results of either are a little surprising:

Id Name OAuthUri
-- ---- --------
 0 2

The number was used for the Name value, instead of the Id that we expected...

The reason for this is actually straightforward, but it's due to a combination of reasons that aren't intuitive.

The bottom line is that the numeric constructor we might have thought we were calling accepts a uint (an unsigned int), rather than a signed int, and literal numbers in PowerShell are always [int] (or [double] if they have a decimal place).

PowerShell (well, programming languages in genera) won't cast implicitly if it looses information. Implicit casting is when you try to pass a value to a method (or function) that accepts a different type of value. Explicit casting is when you specify the type by writing it out.

So for instance, a int will cast implicitly to a double, but not the other way around, because you would loose the fractional value. You can still cast it explicitly, by just specifying [double].

PowerShell simply won't call the method that accepts an unsigned int unless you specifically cast the number to an unsigned int. As far as syntax, you can double-cast it:

[User][uint]2

Or you can call the constructor more explicitly:

[User]::new([uint]2)

Either way, you'll get a user with an Id but no Name.

Id Name OAuthUri
-- ---- --------
 2

Of course, the other part of the puzzle is why you get an unexpected result, instead of an error like:

"InvalidArgument: Cannot convert the "2" value of type "System.Int32" to type "User".

The reason is yet another PowerShell casting oddity: PowerShell can (and will) cast anything to a string implicitly, by calling it's ToString() method. That's really convenient in a shell, where you regularly want to display things as strings, but sometimes... well, something like this happens.

In other words, PowerShell will implicitly cast an integer to string, but will not cast implicitly it to uint -- so you get an unexpected constructor and result.

using System;
public class User
{
public User(UInt32 value)
{
Id = value;
}
public User(String value)
{
Name = value;
}
public User() { }
public UInt32 Id { get; set; }
public String Name { get; set; }
public String OAuthUri { get; set; }
public override string ToString()
{
return Name;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment