Although PowerShell is more or less a "real .NET language", it does extend the common type system with an array of behaviors that sometimes conflict with what you know about .NET's type system behavior from languages like C# or VB.NET.
This additional type system layer bolted on top of the CLR is aptly named the Extended Type System (ETS), and it's directly responsible for making argument conversion "just work" the way you've observed.
When the PowerShell runtime reaches a method invocation statement like $util.GetFirstLine("C:\temp\random-log.txt")
, it has to pick an appropriate overload, just like the C# compiler does.
The first step is to identity overloads for which the number of arguments passed can cover all mandatory parameters of the given overload signature. In your case, only 1 such signature can be resolved at this step, so PowerShell now has two pieces of the puzzle:
- A method stub with the signature
string GetFirstLine(FileInfo fileInfo)
- An argument value of type
[string]
At this point, the C# compiler would give up and report CS1503: Argument 1: cannot convert from 'string' to 'System.IO.FileInfo'.
PowerShell, on the other hand, is designed to be helpful in the hands of system administrators and operators - people who might not put too much thought into data structure design, but who are chiefly concerned with string representations of data (after all, this is what bash
, cmd
, etc. taught them to use).
To meaningfully convert from a string (or any other non-compliant argument type) at just the right time, ETS comes with a number of facilities and hooks for implicit type conversion that the runtime then attempts, one by one, until it either finds a valid conversion mechanism, or fails (at which point the method invocation fails too):
The concrete type converters mentioned above will automatically be respected by ETS if they've either been included in a type data file (see about_Types.ps1xml
), or if the target type is public and the source definition was decorated with a [TypeConverter()]
attribute.
Additionally, you can register new type conversion primitives at runtime with the Update-TypeData
cmdlet.
Of course the madness doesn't stop there, there are also additional facilities specifically for converting/transform command arguments, but that's beyond the scope of this question :)