PowerShell Classes: Is it possible to make a constructors parameter optional?

432 Views Asked by At

I am trying to build a "type", for the Timecode convention used in Film/Animation.

I want to accommodate use cases where a user may want to initialise the object with a Timecode string gotten from elsewhere:

$MyTimeCode = [TimeCode]::new("08:06:04.10", 12)

and in other cases, directly with parameters belonging to the classes constructor:

$MyTimeCode = [TimeCode]::new(, 8, 6, 4, 10, 12)

#If I had constructor such as the foloowing in my class definition:
#TimeCode([String]$TimeStamp, [Int]$Hours, [Int]$Minutes, [Int]$Seconds, [Int]$Frames, [Int]$FrameRate)
`

The following Class as an example:

class HelloWorld {
    [String]$Hello
    [String]$World

    HelloWorld([String]$Hello, [String]$World) {
        $This.Hello = $Hello
        $This.World = $World
        }
    }

Attempting to initialise it [HelloWorld]::new("Hello", "World") works fine but with [HelloWorld]::new("Hello") I get an error:

MethodException: Cannot find an overload for "new" and the argument count: "1".

I have been watching some lectures here and there but they are not structured at all and they quickly advance.

The official docs are usually my go to but the "About_Classes" page quickly advances to "inheriting" and "subclasses" etc with the "Rack" and "Devices" examples and its not clear to me how one begins to work with just a single class.

My goal is to familiarise myself with classes and what they can do. The Timecode is secondary.

3

There are 3 best solutions below

3
lit On BEST ANSWER

A constructor that takes a single argument is needed. There can be as many constructors as needed as long as the signature of the parameters is unique for each. This code also creates a default constructor. The members of the default constructor will contain $null when instantiated.

class HelloWorld {
    [String]$Hello
    [String]$World

    HelloWorld([String]$Hello, [String]$World) {
        $This.Hello = $Hello
        $This.World = $World
    }
    HelloWorld([String]$Hello) {
        $This.Hello = $Hello
        $This.World = 'default'
    }
    HelloWorld() {}     # default constructor
}

$x1 = [HelloWorld]::new('hello', 'world')
$x1
$x2 = [HelloWorld]::new('hello')
$x2
$x3 = [HelloWorld]::new()
$x3
0
mklement0 On

To add some background information to lit's effective solution:

What you're looking for requires one of the following features, neither of which are available in PowerShell, neither in Windows PowerShell (which is no longer active developed), nor in the actively developed PowerShell (Core) (as of v7.3.x, the stable version as of this writing):

  • Optional method/constructor parameters, ideally in combination with named parameters.

    • Optional parameters aren't supported, even though PowerShell lets you define them:

       # !! Does NOT work as of PowerShell 7.3.x
       # !! The default value is QUIETLY ACCEPTED, but IGNORED. 
       class Foo { [void] Bar($baz, $quux = 42) {} }
      
       # !! FAILS, because you must supply arguments for BOTH parameters.
       # !! -> 'Cannot find an overload for "Bar" and the argument count: "1"'
       [Foo]::new().Bar('hi')
      
      • GitHub issue #9701 is a feature request that asks that optional parameters be implemented.
    • Named parameters are fundamentally unsupported.

  • Constructor chaining, i.e. to have multiple constructor overloads that can call each other with default values, which PowerShell doesn't support as of v7.3.x:

       class Foo { 
         # Two-parameter constructor
         Foo($bar, $baz) { <# ... #> } 
         # Single-parameter constructor
         Foo($bar) { 
           # !! Constructor chaining Does NOT work as of PowerShell 7.3.x:
           # !! That is, the attempt to call the two-parameter constructor
           # !! (with a default value for the second parameter) causes a SYNTAX ERROR.
           $this($bar, 'default for $baz') 
         } 
       }
    
    • For a workaround via hidden helper methods, see this answer.

      • In simple cases, if code duplication isn't a concern, you can simply create separate, independent constructor overloads, as shown in lit's answer.
    • GitHub issue #19969 is a feature request asking that constructor chaining be implemented (among other features).

0
Santiago Squarzon On

This is something a C# class would allow you to do, i.e.:

public class HelloWorld
{
    public string? Hello { get; set; }
    public string? World { get; set; }

    public HelloWorld(string? hello = null, string? world = null)
    {
        Hello = hello;
        World = world;
    }
}

Would allow the 3 possibilities (no arguments, 1 argument or 2 arguments) for instantiation in a single constructor. PowerShell classes are far from there... pretty disappointing. On the bright side, Add-Type can be used for inline C#.

Other alternatives to add to the other answers:

class HelloWorld {
    [string] $Hello
    [string] $World
}

[HelloWorld]@{ Hello = 'foo' }
[HelloWorld]@{ World = 'bar' }
class HelloWorld {
    [string] $Hello
    [string] $World

    HelloWorld([string] $Hello, [string] $World) {
        $this.Hello = $Hello
        $this.World = $World
    }

    # targets a `$hello` only explicit casting
    static hidden [HelloWorld] op_Explicit([string] $Hello) {
        return [HelloWorld]::new($Hello, 'myDefaultValue')
    }
}

[HelloWorld] 'foo'

Following the C# example at the beginning, this is also shown in Mathias's helpful answer.

NOTE: PowerShell 7+ is needed to compile, this will fail in Windows PowerShell 5.1.

Add-Type '
public class HelloWorld
{
    public string? Hello { get; set; }
    public string? World { get; set; }

    public HelloWorld() : this("hey", "there")
    { }

    public HelloWorld(string? hello = "hey", string? world = "there")
    {
        Hello = hello;
        World = world;
    }
}' -IgnoreWarnings -WarningAction Ignore

[HelloWorld]::new()

# Hello World
# ----- -----
# hey   there

[HelloWorld]::new('hi')

# Hello World
# ----- -----
# hi    there

[HelloWorld]@{ world = 'you' }

# Hello World
# ----- -----
# hey   you