The PSClasses project provides classes with inheritance to PowerShell versions earlier than 5.0.
PSClasses include important features of object-oriented languages, like following:
| Feature | Example |
|---|---|
class inheritance |
CreateClass "IntCounter" $null '[int] $value' @{ .. }
CreateClass "ModularCounter" -extends "IntCounter" '[int] $modulus' @{
..
} |
overriding methods |
CreateClass "IntCounter" $null '[int] $value' @{
increment = { .. }
}
CreateClass "ModularCounter" -extends "IntCounter" '[int] $modulus' @{
increment = { .. }
} |
calling overridden superclass methods |
CreateClass "IntCounter" $null '[int] $value' @{ increment = { .. } }
CreateClass "ModularCounter" -extends "IntCounter" '[int] $modulus' @{
increment = {
$this.IntCounter_increment()
..
}
} |
multiple constructors |
CreateClass "ModularCounter" -extends "IntCounter" '[int] $modulus' @{
init = { param([int] $value, [int] $modulus) .. }
init0 = { param([int] $modulus) .. }
}
$modCounter = New "ModularCounter" { $self.init(6, 10) }
$modCounter2 = New "ModularCounter" { $self.init0(20) } |
calling other constructors of same class |
CreateClass "ModularCounter" -extends "IntCounter" '[int] $modulus' @{
init = {
param([int] $value, [int] $modulus)
..
}
init0 = {
param([int] $modulus)
$this.ModularCounter_init(0, $modulus)
}
} |
calling superclass constructors |
CreateClass "IntCounter" $null '[int] $value' @{ .. }
CreateClass "BoundedCounter" -extends "IntCounter" '[int] $bound' @{
init = {
param([int] $value, [int] $bound)
# Call generated constructor "init" of superclass
$this.IntCounter_init($value)
..
}
} |
The library also contains an uncommon feature: automatic generation of the constructor which accepts arguments corresponding to all instance variables. This feature resembles case classes in Scala or data classes in Kotlin:
generated constructor |
CreateClass "Config" $null '$drive, $certSubject, $certAccount'
# Constructor 'init' is automatically generated
$config = New "Config" { $self.init("C:", "CN=jsmith", "jsmith") } |
|---|
|
ℹ️
|
The file ClassGenerator_Test.ps1 contains numerous examples in form of Pester tests. |
|
ℹ️
|
The project includes a simple template engine used in implementation of some features. |
We can create a class by calling CreateClass function. The created class is
represented by a class object. A variable holding the class object is
automatically created. The variable name is formed like this: $<className>Class.
The CreateClass function accepts following arguments:
| Argument | Alias | Mandatory |
|---|---|---|
class name |
-name |
yes |
superclass |
-extends |
no |
instance variables |
-variables |
no |
methods |
-methods |
no |
Instance variables are specified as a string containing a comma-separated variable list along with optional type specifiers. The string is parsed and the variable names extracted.
|
ℹ️
|
The string containing instance variables is also used for parameters of the generated constructor named init. |
Syntax is the same as syntax of function parameters or script block parameters.
Methods are written as a dictionary of (name, script block) pairs. The methods dictionary can be an unordered hashtable or ordered dictionary.
A derived class can override superclass methods.
|
ℹ️
|
Each overridden method is available in following form:
<className>_<methodName>.
|
CreateClass "IntCounter" $null '[int] $value' @{
increment = {
$this.value += 1
}
reset = {
$this.value = 0
}
}
CreateClass "ModularCounter" -extends "IntCounter" '[int] $modulus' @{
# Override superclass method
increment = {
If ($this.value -eq $this.modulus - 1) {
# Call inherited method
$this.reset()
} Else {
# Call superclass version
$this.IntCounter_increment()
}
}
}The ModularCounter class overrides increment method. The ModularCounter’s increment method calls the superclass version:
$this.IntCounter_increment()Constructors are special methods whose names start with init. A class can contain multiple constructors. Each constructor can call:
-
any other constructor of the same class, including the generated constructor
-
any superclass constructor
|
ℹ️
|
When a custom constructor calls other constructors it must use one of following forms:
|
The constructor named init is generated automatically. It accepts arguments corresponding to all instance variables (including instance variables declared in superclasses) and just copies the arguments into instance variables.
|
ℹ️
|
The generated constructor can be overridden by a custom init constructor. If overridden, the generated constructor is still available to be
called from other constructors as a method with following name:
|
Let’s look again at the above example which creates Person class:
CreateClass "Person" $null '[string] $name, [int] $age' @{
# Constructor 'init' is automatically generated
getInfo = {
"Name: $($this.name), age: $($this.age)"
}
}The Person’s methods don’t include custom constructors. The generated constructor init is available after class creation. Its arguments correspond to instance variables:
-
$name
-
$age
We can immediately create new objects using the generated init constructor:
$person = New "Person" { $self.init("John Smith", 23) }
> $person
name age
---- ---
John Smith 23Let’s rewrite IntCounter and ModularCounter classes to include only the constructor methods:
CreateClass "IntCounter" $null '[int] $value' @{
init0 = {
$this.value = 0
}
}The IntCounter’s generated constructor init accepts [int] $value
parameter. The class also includes a parameterless constructor init0.
CreateClass "ModularCounter" -extends "IntCounter" '[int] $modulus' @{
# Same as generated constructor, but with argument checks
init = {
param([int] $value, [int] $modulus)
# Call generated constructor
$this.ModularCounter_gen_init($value, $modulus)
If ($modulus -lt 1) {
throw "ModularCounter: modulus bad"
}
If ($value -lt 0 -or $value -gt $modulus) {
throw "ModularCounter: value bad"
}
}
# A simplified constructor
init0 = {
param([int] $modulus)
# Call another constructor
$this.ModularCounter_init(0, $modulus)
}
}The ModularCounter’s generated constructor init which accepts the
parameters [int] $value and [int] $modulus is overridden by the
custom init constructor. The custom init constructor calls the
generated init constructor:
$this.ModularCounter_gen_init($value, $modulus)The class also includes a parameterless constructor init0 which calls the custom init constructor:
$this.ModularCounter_init(0, $modulus)The following classes are a part of an example which illustrates the chain of responsibility design pattern. For simplicity we have excluded non-constructor methods.
CreateClass "HelpHandler" $null '[PSCustomObject] $successor, [string] $topic'
CreateClass "Widget" -extends "HelpHandler" '[PSCustomObject] $parent' @{
init = {
param([PSCustomObject] $parent, [string] $topic)
# Widget's parent is HelpHandler's successor
$this.HelpHandler_init($parent, $topic)
$this.parent = $parent
}
}The Widget’s init constructor calls the generated constructor of the HelpHandler superclass:
$this.HelpHandler_init($parent, $topic)There are several ways to create new objects. The simplest way is to call the function New (or alternatively New_). Another way is to call the method new (or alternatively new_) on the class object).
Let’s illustrate creation of objects on the following simple class:
CreateClass "Point" $null '[double] $x, [double] $y' @{
translate = {
param([double] $x, [double] $y)
$this.x += $x
$this.y += $y
}
scale = {
param([double] $factor)
$this.x *= $factor
$this.y *= $factor
}
}The function New accepts two arguments: a class name and a script block containing a constructor call, e.g.:
$point = New "Point" { $self.init(10, 20) }Here we supply a parameterless script block. When function New is
called it will create $self object and then perform the call
$self.init(10, 20) on the object.
|
❗
|
The function New expects the supplied script block to contain a constructor call on the object $self. If we use any other object it will not work. |
|
|
The way of object creation with function New will not work correctly within closures. For example: $script = {
...
# This will not work
$point = New "Point" { $self.init(-10, -50) }
...
}.GetNewClosure() |
The function New_ is similar to New but it expects a script block with a single parameter representing the object being created and initialized. The parameter name is irrelevant.
For example:
$point1 = New_ "Point" { param($self) $self.init(30, 50) }
$point2 = New_ "Point" { param($_) $_.init(25, 35) }|
ℹ️
|
The way of object creation with function New_ will work correctly within closures. |