10

Why is it that, in Powershell, the System.DayOfWeek enum can be referred to like [System.DayOfWeek], whereas the System.Environment.SpecialFolder enum must be referred to like [System.Environment+SpecialFolder] (note the plus character)?

My guess is because SpecialFolder is part of the static Environment class and DayOfWeek is sitting directly in the System namespace, but I'm having trouble finding any information on this. Normally static members would use the "static member operator", but that doesn't work in this case, nor does anything else I try except the mysterious plus character...

[System.DayOfWeek] # returns enum type
[enum]::GetValues([System.DayOfWeek]) # returns enum values
[enum]::GetValues([System.Environment.SpecialFolder]) # exception: unable to find type
[enum]::GetValues([System.Environment]::SpecialFolder) # exception: value cannot be null
[enum]::GetValues([System.Environment+SpecialFolder]) # returns enum values

System.Environment.SpecialFolder is definitely a type, and in C# both enums work the same way:

Enum.GetValues(typeof(System.Environment.SpecialFolder)) // works fine
Enum.GetValues(typeof(System.DayOfWeek)) // also works

I'd really like to understand why there's a distinction in Powershell and the reasoning behind this behaviour. Does anyone know why this is the case?

Matt Wanchap
  • 841
  • 8
  • 20
  • 8
    `+` is nested type separator. https://learn.microsoft.com/dotnet/framework/reflection-and-codedom/specifying-fully-qualified-type-names – user4003407 Jul 27 '19 at 02:40
  • 1
    Thanks, at least now I know what it is! But I'm still having trouble understanding why this separator is necessary to get the type in Powershell but is not required in C#. Are all Powershell type statements actually processed internally by some sort of reflection method that requires this syntax, whereas perhaps C# statements are not since the compiler handles parsing them into IL instructions or something like that? Actually, I wonder what they look like in IL... – Matt Wanchap Jul 27 '19 at 05:54
  • 2
    In IL it looks like `[mscorlib]System.Environment/SpecialFolder` – Matt Wanchap Jul 27 '19 at 06:06
  • `Get-Type -TypeName SpecialFolder -Force | Select-Object -Property Name, FullName` should answer… – JosefZ Jul 27 '19 at 12:19
  • 7
    @MattWanchap The big difference is that PowerShell resolves type names at runtime - so having a distinct separator specifically for nested types means faster type name resolution, since PowerShell then doesn't have to attempt to resolve the parent type name as a namespace and search that as well. – Mathias R. Jessen Jul 27 '19 at 12:48
  • @JosefZ thanks for the idea, I ended up with this: `[AppDomain]::CurrentDomain.GetAssemblies() | % { $_.GetTypes() | ? { $_.FullName -like "*+*" } | select -Property FullName }` @MathiasR.Jessen great explanation! – Matt Wanchap Jul 27 '19 at 20:56

1 Answers1

6

System.Environment.SpecialFolder is definitely a type

Type SpecialFolder, which is nested inside type Environment, is located in namespace System:

  • C# references that type as a full type name as in the quoted passage; that is, it uses . not only to separate the namespace from the containing type's name, but also to separate the latter from its nested type's name.

  • By contrast, PowerShell uses a .NET reflection method, Type.GetType(), to obtain a reference to the type at runtime:

    • That method uses a language-agnostic notation to identify types, as specified in documentation topic Specifying fully qualified type names.Tip of the hat to PetSerAl.

    • In that notation, it is + that is used to separate a nested type from its containing type (not ., as in C#).

That is, a PowerShell type literal ([...]) such as:

[System.Environment+SpecialFolder]

is effectively the same as taking the content between [ and ], System.Environment+SpecialFolder, and passing it as a string argument to Type.GetType, namely (expressed in PowerShell syntax):

[Type]::GetType('System.Environment+SpecialFolder')

Note that PowerShell offers convenient extensions (simplifications) to .NET's language-agnostic type notation, notably the ability to use PowerShell's type accelerators (such as [regex] for [System.Text.RegularExpressions.Regex]), the ability to omit the System. prefix from namespaces (e.g. [Collections.Generic.List`1[string]] instead of [System.Collections.Generic.List`1[string]]), and not having to specify the generic arity (e.g. `1) when a list of type argument is passed (e.g. [Collections.Generic.List[string]] instead of [Collections.Generic.List`1[string]] - see this answer) for more information.

mklement0
  • 382,024
  • 64
  • 607
  • 775