23

I was doing some testing and stumbled upon the following:

You can overload methods in PoShv5 as you wish. If you call the method without parameters, it can internally call the method with parameters, to keep your code non-redundant. I expected this to be also true for constructors.

In this example, the last constructor is working as expected. The other constructors only return objects without set values.

Class car {
    [string]$make
    [string]$model
    [int]$Speed
    [int]$Year

    speedUp (){
        $this.speedUp(5)
    }
    speedUp ([int]$velocity){
        $this.speed += $velocity
    }

    # Constructor
    car () {
        [car]::new('mall', $Null, $null)
    }

    car ([string]$make, [string]$model) {
        [car]::new($make, $model, 2017)
    }

    car ([string]$make, [string]$model, [int]$Year) { 
        $this.make = $make
        $this.model = $model
        $this.Year = $year
    }
}

[car]::new() # returns "empty" car
[car]::new('Make', 'Nice model') # returns also an "empty" one
[car]::new( 'make', 'nice model', 2017) # returns a "filled" instance

Is there a way to fix this? Did I miss something?

mklement0
  • 382,024
  • 64
  • 607
  • 775
restless1987
  • 1,558
  • 10
  • 16
  • 1
    What you're looking for is called constructor chaining, and no, PowerShell doesn't seem to have any syntax for this :-| You'll just have to re-implement the member assignments in each constructor definition – Mathias R. Jessen Jun 07 '17 at 12:59

2 Answers2

37

To complement Mathias R. Jessen's helpful answer:

The recommended approach is to use hidden helper methods to compensate for the lack of constructor chaining:

Class car {

    [string]$Make
    [string]$Model
    [int]$Year

    speedUp (){
        $this.speedUp(5)
    }
    speedUp ([int]$velocity){
        $this.speed += $velocity
    }

    # Hidden, chained helper methods that the constructors must call.
    hidden Init([string]$make)                 { $this.Init($make, $null) }
    hidden Init([string]$make, [string]$model) { $this.Init($make, $model, 2017) }
    hidden Init([string]$make, [string]$model, [int] $year) {
        $this.make = $make
        $this.model = $model
        $this.Year = $year
    }

    # Constructors
    car () {
        $this.Init('Generic')
    }

    car ([string]$make) {
        $this.Init($make)
    }

    car ([string]$make, [string]$model) {
        $this.Init($make, $model)
    }

    car ([string]$make, [string]$model, [int]$year) { 
        $this.Init($make, $model, $year)
    }
}

[car]::new()                          # use defaults for all fields
[car]::new('Fiat')                    # use defaults for model and year
[car]::new( 'Nissan', 'Altima', 2015) # specify values for all fields

This yields:

Make    Model  Year
----    -----  ----
Generic        2017
Fiat           2017
Nissan  Altima 2015

Note:

  • The hidden keyword is more of a convention that PowerShell itself observes (such as omitting such members when outputting); members tagged this way are technically still accessible, however.

  • While you can't call a constructor of the same class directly, it is possible to do so with a base-class constructor, using C#-like syntax.

mklement0
  • 382,024
  • 64
  • 607
  • 775
15

TL;DR: No!


What you're looking for (overloaded constructors calling each other in succession) is also colloquially known as constructor chaining, and looks roughly like this in C#:

class Car
{
    string Make;
    string Model;
    int Year;

    Car() : this("mall", null)
    {
    }

    Car(string make, string model) : this(make, model, 2017) 
    {
    }

    Car(string make, string model, int Year) 
    { 
        this.Make = make;
        this.Model = model;
        this.Year = year;
    }
}

Unfortunately, PowerShell doesn't seem to have any syntax for this - you can't do:

Car() : $this("Porsche") {}
Car([string]$Make) {}

without having the parser throw up at you for missing the body definition of your constructor, and I don't expect to see it anytime soon - the PowerShell team has expressed an explicit desire not to become the maintainers of a new watered down C# - which I can perfectly well understand :-)

You'll just have to re-implement the member assignments in each constructor definition.

Community
  • 1
  • 1
Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206