Reputation: 23623
I would like to create a custom dictionary class in PowerShell (which temporary stores a configuration in a specific 3rd party application and retrieves it in another PowerShell session). Technically, I could also do this by creating a kind of Set-MyConfigVariable -Key <key> -Value <Value>
and Get-MyConfigVariable -Key <key>
cmdlets, but I would like to investigate if I can make in more user (author) frindly with a extended dictionary (and hook in the getter
and setter
methods).
In short, something that is discribed here in C#: How to write a getter and setter for a Dictionary?
Class MyStore: System.Collections.Generic.Dictionary[String,String] {
$h = @{}
[string]$This([String]$Key) {
get { write-host 'getting something'; return $h[$Key] }
set { write-host 'setting something'; $h[$Key] = $Value }
}
}
$MyStore = [MyStore]::new()
$MyStore['key'] = 'value'
$MyStore['key']
The above prototype fails at [string]$This([String]$Key) {
(the indexer?) but probably also need a different implementation for the getter and setter (using Update-TypeData
?).
How can this be done in PowerShell (if even possible)?
If not possible for a class, can it be done on an single instance (with dynamic keys)?
Upvotes: 2
Views: 316
Reputation: 437100
PowerShell code, using custom class
es, has the following limitations as of PowerShell 7.3.4:
There's generally no support for property getters and setters - GitHub issue #2219 proposes adding support for them.
For now, getters and setters for regular properties can be emulated, using Add-Member
with a ScriptProperty
member, as shown in this answer.Tip of the hat to Santiago Squarzon.
However, there is no workaround for implementing parameterized properties, as is required for implementing indexers.
C# code, ad hoc-compiled with Add-Type
, offers a solution:
Deriving your custom class from Dictionary[String,String]
- as you've attempted - is problematic, because it requires hiding the base class members with the new
keyword, which in PowerShell code - at least as of PowerShell 7.3.4 - isn't fully supported - see GitHub issue #19649.
However, you can make your custom class wrap a dictionary type, and provide access to it via a type-specific indexer (parameterized property), into which you can hook PowerShell script blocks to execute.
See the following sample implementation:
Add-Type @'
using System;
using System.Collections.Generic;
using System.Management.Automation;
public class CustomDict
{
// The dictionary to wrap.
Dictionary<string, string> _dict = new ();
// The hook script blocks.
ScriptBlock _getHook, _setHook;
// Constructor
public CustomDict(ScriptBlock getHook = null, ScriptBlock setHook = null)
{
_getHook = getHook; _setHook = setHook;
}
// Implement an indexer that provides access to that of the wrapped
// dictionary, while also executing the "event"-hook script blocks.
public string this[string key]
{
get {
_getHook?.Invoke(_dict, key);
return _dict[key];
}
set {
_setHook?.Invoke(_dict, key, value);
_dict[key] = value;
}
}
}
'@
# Construct an instance with both GET and SET hooks (script blocks).
$o = [CustomDict]::new(
{ param($dict, $key) Write-Verbose -Verbose "getting entry '$key'" },
{ param($dict, $key, $value )Write-Verbose -Verbose "setting '$key' to '$value'" }
)
# Create an entry...
$o['foo'] = 'bar'
# ... and report its value
$o['foo']
Output:
VERBOSE: setting 'foo' to 'bar'
VERBOSE: getting entry 'foo'
bar
Note:
Upvotes: 2