Search code examples
powershellclassconstructorinitialization

Powershell Collection Class Constructor


I have been working on a scripting project that involves creating custom PowerShell classes. I am currently trying to understand the following syntax

$Temp = [myclass[]]::new(1)

This syntax seems to create a generic List of some kind where I can specify the size of the List. Is there any way for me to modify the constructor for this? Ideally I would like to do something like the following:

[string[]]$ClassData = @('test1','test2','test3')
[myclass[]]$Temp = [myclass[]]::new($ClassData)

And this would call the constructor for myclass for each of the items in $ClassData. Unfortunately this is not the way that it works currently and I have been working around the situation by using the following:

[string[]]$ClassData = @('test1','test2','test3')
[myclass[]]$Temp = @($ClassData | [myclass[]]::new($_))

Any assistance or explanations as to how to make all this work would be greatly appreciated


Solution

  • Note: I'm assuming that your working example meant to use [myclass]::new($_), not [myclass[]]::new($_), i.e. that you're creating a single [myclass] instance in each loop iteration: @($ClassData | % { [myclass]::new($_) })


    Instead of trying to pass the array of initialization values to the static ::new() method (i.e., to a constructor behind the scenes), cast it:

    [myclass[]]$Temp = $ClassData
    

    Note that [myclass[]] is an array of instances of type [myclass], and if [myclass]::new($_) works with $_ representing a single string, then the above cast should succeed.

    Essentially, PowerShell will do behind the scenes what your loop-based solution does explicitly.


    As for what you tried:

    [myclass[]]::new($ClassData) doesn't work, because [array] (System.Array) doesn't have a constructor that accepts an existing array to initialize the new array with; the only constructor is the one that accepts the size (length, element count) of the array, which you can verify as follows:

    PS> [object[]]::new
    
    OverloadDefinitions
    -------------------
    System.Object[] new(int )
    

    Optional reading: casting from hashtables / custom objects:

    Casts are very flexible, much more so than in C#, for instance.

    Even if the input type is not directly convertible to the target type and the target type doesn't have a single-argument constructor for the input type, PowerShell is still able to construct instances of the target type if all of the following conditions are met:

    • The target type has a public constructor that is parameter-less.[1]
    • The input type has the same set of properties as the target type or a subset of them.
    • All overlapping properties are type-compatible themselves (properties are of the same type or can be converted from the input property type to the target property type).

    Here's an example of initializing an array of a custom class (using a PSv5+ class definition) from an array containing a hashtable and a custom object ([pscustomobject]) that each provide a subset of the target type's properties.

    # Define a class.
    # Not defining a constructor explicitly implicitly defines
    # a public, parameter-less one.
    class Foo {
      [string] $Bar
      [int]    $Baz
    }
    
    # Create an array of [Foo] instances via initialization by a
    # hashtable and a custom object.
    [Foo[]] $fooArr = @{ Bar = 'None' }, [pscustomobject] @{ Baz = 42 }
    

    Outputting $fooAr afterwards yields:

    Bar  Baz
    ---  ---
    None   0
          42
    

    That is, two [Foo] instances were successfully constructed from the input objects.

    For more information on casts and type conversions in PowerShell, see this answer.


    [1] An approved future enhancement to PowerShell Core will allow initialization from hashtables / custom objects even with constructors that have parameters, as long as there is a matching constructor overload for the input set of hashtable entries / custom-object properties.