powershellconstructorhashtableobject-initializers

Hashtable key with point


How do I issue a hash table key with a point character, such as FlatAppearance.BorderSize, so that it is correct?

$Button = [System.Windows.Forms.Button] @{

    # This is not a valid entry
    FlatAppearance.BorderSize = 0
}
$Button = [System.Windows.Forms.Button] @{

    # This is not a valid entry too
    "FlatAppearance.BorderSize" = 0
}
$Button = [System.Windows.Forms.Button] @{

    # This is not a valid entry too too
    FlatAppearance = @{ BorderSize = 0 }
}

Of course, I can write it like that

$Button = [System.Windows.Forms.Button] @{}
$Button.FlatAppearance.BorderSize = 0

However, it is more convenient to write inside the hash table. But how? Thanks


Solution

  • Since the Button type's .FlatAppearance property is of type FlatButtonAppearance, there's no good solution in this case, because the FlatButtonAppearance type has no public, parameterless constructor.

    If it did, you would be able to write:

    using namespace System.Windows.Forms
    
    $Button = [Button] @{
    
        # !! This is how you would generally do it, but
        # !! IN THIS CASE IT DOESN'T WORK, due to lack of an appropriate constructor.
        FlatAppearance = [FlatButtonAppearance] @{ BorderSize = 0 }
    }
    

    The above syntax is akin to C#'s object initializer syntax, explained in the next section.


    Object initializer syntax in PowerShell:

    For a cast to a type literal ([...]) from a hashtable (@{ ... }) or preexisting [pscustomobject] instance[1] to perform implicit construction (implicit creation of an instance) and then multi-property initialization to work, the following prerequisites must be met:

    This allows PowerShell to create an instance behind the scenes simply by calling new SomeType() ([SomeType]::new() in PowerShell syntax), followed by assigning the public properties' values from the hashtable entries of the same name.

    Note: Such casts, available in PowerShell v3+, are in effect syntactic sugar for calling the
    New-Object cmdlet with the -Property parameter; only the latter works in v2.


    Aside from consulting a type's documentation, you can easily inspect a type's constructors in PowerShell by calling the PowerShell-supplied static ::new method without parentheses (()):

    PS> [System.Windows.Forms.FlatButtonAppearance]::new
    # NO OUTPUT, which means the type has no public constructors at all.
    
    # [ProcessStartInfo] has several public constructors, among them
    # a public parameterless one, so you *can* initialize it by hashtable.
    PS> [System.Diagnostics.ProcessStartInfo]::new
    
    OverloadDefinitions
    -------------------
    System.Diagnostics.ProcessStartInfo new()
    System.Diagnostics.ProcessStartInfo new(string fileName)
    System.Diagnostics.ProcessStartInfo new(string fileName, string arguments)
    
    

    To determine a type's public, writeable instance properties:

    PS> [System.Windows.Forms.FlatButtonAppearance].GetProperties('Public, Instance') | 
           ? CanWrite | Select-Object Name, PropertyType
    
    Name               PropertyType        
    ----               ------------        
    BorderSize         System.Int32        
    BorderColor        System.Drawing.Color
    CheckedBackColor   System.Drawing.Color
    MouseDownBackColor System.Drawing.Color
    MouseOverBackColor System.Drawing.Color
    

    [1] For instance, this is useful for objects deserialized from JSON, which become [pscustomobject] instances; e.g.: $obj = '{ "Text": "Submit" }' | ConvertFrom-Json; $button = [Button] $obj

    [2] Edge case: There mustn't also be a single-parameter public constructor with one of the following parameter types, because it would bind the cast operand as-is:

    [3] Conveniently, if you define a type (class) without declaring any constructor, you get a public parameterless one by default; e.g., in the following PowerShell example class Foo implicitly provides such a constructor:
    class Foo { [string] $Bar }; $foo = [Foo] @{ Bar = 'None' }