Monday, October 19, 2015

The New “New” in PowerShell 5 is more useful than it looks

In PowerShell 5, we have a new way to create a new object. At first glance, it doesn’t seem very useful, because we already have a simple enough way to do that.

We could always do this.

001
New-Object DateTime ( 2015, 10, 31 )
Saturday, October 31, 2015 12:00:00 AM

With PowerShell 5, they added the static ::New() method to every .Net class. (This was done at the PowerShell level. This method does not exist natively in .Net. PowerShell’s “Adaptive Type System” allows you to enhance any .Net definition, or even all of them.)

So now we can do this.

001
[DateTime]::New( 2015, 10, 31 )
Saturday, October 31, 2015 12:00:00 AM

At first glance, you might wonder why the PowerShell team resources weren’t used on something else. Anything else. But there is one thing that ::New() gives us that New-Object does not: discoverability of constructor definitions.

The ultimate resource for the method and constructor definitions (or “overloads”), for most .Net objects is MSDN.Microsoft.com. Just Google “MSDN datetime” (without the quotes) and there you are.

But that’s work. And I’m lazy. Plus sometimes all I need is just a quick cheat sheet for the object.

Method overloads (definitions)

For methods, we have long had a shortcut for getting a list of the overloads for a method and their syntax. Simply call the method WITHOUT parentheses.

This gives us the current date and time converted to a string in a default format based on my computer’s localization:

001
(Get-Date).ToString()
10/17/2015 12:36:48 PM

And this give us a list of the many .ToString() overloads that have been defined for datetime objects:

001
(Get-Date).ToString
OverloadDefinitions
-------------------
string ToString()
string ToString(string format)
string ToString(System.IFormatProvider provider)
string ToString(string format, System.IFormatProvider provider)
string IFormattable.ToString(string format, System.IFormatProvider formatProvider)
string IConvertible.ToString(System.IFormatProvider provider)


We’re not going to understand all of that without doing some research, and I suspect most of it is not helpful to us. But that second definition tells us that instead of accepting the default format, we can give it a string with a custom format specification. So after a little experimenting, we can create a name for a debug log like this.

001
(Get-Date).ToString( "'DebugLog.'yyyy.MM.dd-hh.mm.ss.'txt'" )
DebugLog.2015.10.17-12.38.37.txt

Constructor overloads

But constructor overloads have always been harder to get. They are buried deep in the type definition, and we can theoretically pull them out using PowerShell, but it isn’t pretty. And it’s work. And I’m lazy. With PowerShell 5, the work is all done for us.

Because the new ::New() method is a method, we can get the overloads using the same syntax as we did with .ToString(). Mostly. .ToString() is a dynamic method, so we call it from an instance of an object, using a period.  ::New() is a static method, so we call it directly from the type definition, using two colons.

But the same parentheses rule still applies.

This will give us a new datetime object.

001
[DateTime]::New( 2015, 10, 31 )
Saturday, October 31, 2015 12:00:00 AM


This gives us the list of constructors for datetime objects, the different sets of parameters we can use with ::New() or New-Object to create a new datetime object. (In case you are just skimming the article, this is it. This is the useful thing you can use the new ::New() method for.)

001
[DateTime]::New
OverloadDefinitions
-------------------
datetime new(long ticks)
datetime new(long ticks, System.DateTimeKind kind)
datetime new(int year, int month, int day)
datetime new(int year, int month, int day, System.Globalization.Calendar calendar)
datetime new(int year, int month, int day, int hour, int minute, int second)
datetime new(int year, int month, int day, int hour, int minute, int second, System.DateTimeKind kind)
datetime new(int year, int month, int day, int hour, int minute, int second, System.Globalization.Calendar calendar)
datetime new(int year, int month, int day, int hour, int minute, int second, int millisecond)
datetime new(int year, int month, int day, int hour, int minute, int second, int millisecond, System.DateTimeKind kind)
datetime new(int year, int month, int day, int hour, int minute, int second, int millisecond, System.Globalization.Calendar calendar)
datetime new(int year, int month, int day, int hour, int minute, int second, int millisecond, System.Globalization.Calendar calendar, System.DateTimeKind kind)


You can see, for example, why it would fail if you tried [datetime]::New() with no parameters. For DateTime objects, there is no constructor definition with no parameters.

Meanwhile, back at the ranch

Now even though we now have two (count them: two) different ways to do the exact same thing, we are not limited to just these two. We can also just tell PowerShell that we want an object to be a different type and it will do its best to oblige. In fact, it will try ten different ways to make the conversion happen, and trying to find an appropriate constructor in the target type definition is only one of them.

The following works despite not having a matching constructor. (The precise interpretation of the string will depend on your computer’s localization settings.)

001
[DateTime]'10/12/15'
Monday, October 12, 2015 12:00:00 AM