Background
I have a data object in PowerShell with 4 properties, 3 of which are strings and the 4th a hashtable. I would like to arrange for a new type that is defined as a collection of this data object.
In this collection class, I wish to enforce a particular format that will make my code elsewhere in the module more convenient. Namely, I wish to override the add method with a new definition, such that unique combinations of the 3 string properties add the 4th property as a hashtable, while duplicates of the 3 string properties simply update the hashtable property of the already existing row with the new input hashtable.
This will allow me to abstract the expansion of the collection and ensure that when the Add method is called on it, it will retain my required format of hashtables grouped by unique combinations of the 3 string properties.
My idea was to create a class that extends a collection, and then override the add method.
Code so far
As a short description for my code below, there are 3 classes:
- A data class for a namespace based on 3 string properties (which I can reuse in my script for other things).
- A class specifically for adding an id property to this data class. This id is the key in a hashtable with values that are configuration parameters in the namespace of my object.
- A 3rd class to handle a collection of these objects, where I can define the add method. This is where I am having my issue.
Using namespace System.Collections.Generic
Class Model_Namespace {
[string]$Unit
[string]$Date
[string]$Name
Model_Namespace([string]$unit, [string]$date, [string]$name) {
$this.Unit = $unit
$this.Date = $date
$this.Name = $name
}
}
Class Model_Config {
[Model_Namespace]$namespace
[Hashtable]$id
Model_Config([Model_Namespace]$namespace, [hashtable]$config) {
$this.namespace = $namespace
$this.id = $config
}
Model_Config([string]$unit, [string]$date, [string]$name, [hashtable]$config) {
$this.namespace = [Model_Namespace]::new($unit, $date, $name)
$this.id = $config
}
}
Class Collection_Configs {
$List = [List[Model_Config]]@()
[void] Add ([Model_Config]$newConfig ){
$checkNamespaceExists = $null
$u = $newConfig.Unit
$d = $newConfig.Date
$n = $newConfig.Name
$id = $newConfig.id
$checkNamespaceExists = $this.List | Where { $u -eq $_.Unit -and $d -eq $_.Date -and $n -eq $_.Name }
If ($checkNamespaceExists){
($this.List | Where { $u -eq $_.Unit -and $d -eq $_.Date -and $n -eq $_.Name }).id += $id
}
Else {
$this.List.add($newConfig)
}
}
}
Problem
I would like the class Collection_Configs to extend a built-in collection type and override the Add method. Like a generic List<> type, I could simply output the variable referencing my collection and automatically return the collection. This way, I won't need to dot into the List property to access the collection. In fact I wouldn't need the List property at all.
However, when I inherit from System.Array, I need to supply a fixed array size in the constructor. I'd like to avoid this, as my collection should be mutable. I tried inheriting from List, but I can't get the syntax to work; PowerShell throws a type not found error.
Is there a way to accomplish this?
Update
After mklement's helpful answer, I modified the last class as:
Using namespace System.Collections.ObjectModel
Class Collection_Configs : System.Collections.ObjectModel.Collection[Object]{
[void] Add ([Model_Config]$newConfig ){
$checkNamespaceExists = $null
$newConfigParams = $newConfig.namespace
$u = $newConfigParams.Unit
$d = $newConfigParams.Date
$n = $newConfigParams.Name
$id = $newConfig.id
$checkNamespaceExists = $this.namespace | Where { $u -eq $_.Unit -and $d -eq $_.Date -and $n -eq $_.Name }
If ($checkNamespaceExists){
($this | Where { $u -eq $_.namespace.Unit -and $d -eq $_.namespace.Date -and $n -eq $_.namespace.Name }).id += $id
}
Else {
([Collection[object]]$this).add($newConfig)
}
}
}
Which seems to work. In addition to the inheritance, had to do some other corrections regarding how I dotted into my input types, and I also needed to load the collection class separately after the other 2 classes as well as use the base class's add method in my else statement.
Going forward, I will have to do some other validation to ensure that a model_config type is entered. Currently the custom collection accepts any input, even though I auto-convert the add parameter to model_config, e.g.,
$config = [model_config]::new('a','b','c',@{'h'='t'})
$collection = [Collection_Configs]::new()
$collection.Add($config)
works, but
$collection.Add('test')
also works when it should fail validation. Perhaps it is not overriding correctly and using the base class's overload?
Last update
Everything seems to be working now. The last update to the class is:
using namespace System.Collections.ObjectModel
Class Collection_Configs : Collection[Model_Config]{
[void] Add ([Model_Config]$newConfig ){
$checkNamespaceExists = $null
$namespace = $newConfig.namespace
$u = $namespace.Unit
$d = $namespace.Date
$n = $namespace.Name
$id = $newConfig.id
$checkNamespaceExists = $this.namespace | Where { $u -eq $_.Unit -and $d -eq $_.Date -and $n -eq $_.Name }
If ($checkNamespaceExists){
($this | Where { $u -eq $_.namespace.Unit -and $d -eq $_.namespace.Date -and $n -eq $_.namespace.Name }).id += $id
}
Else {
[Collection[Model_Config]].GetMethod('Add').Invoke($this, [Model_Config[]]$newConfig)
}
}
}
Notice in the else statement that ....GetMethod('Add')...
is necessary for Windows PowerShell, as pointed out in the footnote of mklement0's super useful and correct answer. If you are able to work with Core, then mklement0's syntax will work (I tested).
Also mentioned by mklement0, the types need to be loaded separately. FYI this can be done on the commandline for quick provisional testing by typing in the model_namespace and model_config classes and pressing enter before doing the same for Collection_Configs.
In summary this will create a custom collection type with custom methods in PowerShell.