5

A System.IO.FileInfo has a Target member.

Using Get-Item -Path * -Include 't.txt' | Get-Member shows it to have a Target member that is a CodeProperty.

Using GetType() shows it to be a List`1

C:>Get-Item -Path * -Include 't.txt' | ForEach-Object { $_.Target.GetType() }

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     List`1                                   System.Object

C:>Get-Item -Path * -Include 't.txt' | % { $_.Target.GetType() | % { $_.FullName } }
System.Collections.Generic.List`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
mklement0
  • 382,024
  • 64
  • 607
  • 775
lit
  • 14,456
  • 10
  • 65
  • 119
  • 2
    It's a [generic](https://learn.microsoft.com/en-us/dotnet/standard/generics/), a type-safe way to store objects without hard-coding types in advance. – vonPryz Mar 02 '19 at 17:47
  • Also see: .net generics, ildasm, anonymous methods/lambdas/delegates, function/data representations in c# debugger (call stack/locals) for example, etc. – Kory Gill Mar 02 '19 at 19:20
  • As an aside: strictly speaking, the `.Target` property is defined in terms of an _interface_, namely (using C# notation) `IEnumerable` (verify with `([System.IO.FileInfo]::new('/') | Get-Member Target).Definition`). The _implementing type_ is `List`. – mklement0 Mar 02 '19 at 21:11

1 Answers1

9

vonPryz has provided the crucial pointer in a comment: List`1 is .NET's representation of a generic type named List with arity (`) 1, i.e., a generic type with 1 type parameter.

(The use of ` in this context is unrelated to PowerShell's use of ` as the escape character).

In your case, System.Collections.Generic.List`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] indicates that the generic type was closed (instantiated) with type System.String.

Leaving the assembly qualification aside (mscorlib, Version = ...), the equivalent PowerShell representation is System.Collections.Generic.List`1[[string]], which can be simplified in two ways, however:

  • The arity indicator, `1, can be omitted, given that the arity is implied by the type argument in [...], [string].
  • Given that there's only 1 type parameter, you can omit the outer [...] around the type argument list.

Therefore, you can use just System.Collections.Generic.List[string], or, expressed as a PowerShell type literal ([...]), [System.Collections.Generic.List[string]]


Optional reading: Shortening type names and literals in PowerShell:

[System.Collections.Generic.List[string]] is a bit unwieldy, and there are two ways to shorten it:

  • PowerShell allows you omit the System. part of the namespace for any type, so [Collections.Generic.List[string]] works too.

  • PowerShell v5+ offers the using namespace statement, analog to C#'s using statement:

      # Note:
      #  * `using namespace` must be at the *start* of the script (potentially
      #    preceded by other `using` statements and comments only)
      #  * The 'System.' part of a namespace must *not* be omitted.
      using namespace System.Collections.Generic
    
      [List[string]] # short for: [System.Collections.Generic.List[string]]
    

Additionally, PowerShell has built-in type accelerators for certain often-used types, which are single-component names that refer to specific types without needing to specify their namespace of origin; e.g., [xml] is a type accelerator for [System.Xml.XmlDocument].

This TechNet blog post shows that you can use the following command to list all built-in type accelerators:

[psobject].Assembly.GetType("System.Management.Automation.TypeAccelerators")::
  get.GetEnumerator() | Sort-Object Key

As TheIncorrigible1 points out, you can even define your own type accelerators with the ::Add() method; e.g., the following command defines [cmdinfo] as an accelerator for type [System.Management.Automation.CommandInfo]:

[psobject].Assembly.GetType("System.Management.Automation.TypeAccelerators")::
      Add('cmdinfo', [System.Management.Automation.CommandInfo])

The new accelerator will be available session-globally (but only for the current session), even if the call is made from a child scope.

That said, there's a good reason not to do this:

  • System.Management.Automation.TypeAccelerators is not a public type, so it isn't part of PowerShell's public API, and therefore not guaranteed to be there (in its present form) in the future.

  • Therefore, the using namespace approach is preferable.

mklement0
  • 382,024
  • 64
  • 607
  • 775