如何在集合类中扩展和重写 Add



>Background

我在 PowerShell 中有一个数据对象,其中包含 4 个属性,其中 3 个是字符串,第 4 个是哈希表。我想安排一个定义为此数据对象的集合的新类型。

在这个集合类中,我希望强制实施一种特定的格式,这将使我的代码在模块中的其他位置更方便。也就是说,我希望用新的定义覆盖 add 方法,这样 3 个字符串属性的唯一组合将第 4 个属性添加为哈希表,而 3 个字符串属性的副本只是使用新的输入哈希表更新现有行的哈希表属性。

这将允许我抽象集合的扩展,并确保当调用 Add 方法时,它将保留我所需的哈希表格式,这些哈希表按 3 个字符串属性的唯一组合分组。

我的想法是创建一个扩展集合的类,然后重写 add 方法。

到目前为止的代码

作为下面我的代码的简短描述,有 3 个类:

  1. 基于 3 个字符串属性的命名空间的数据类(我可以在我的脚本中重用这些属性来处理其他事情)。
  2. 专门用于向此数据类添加 id 属性的类。此 id 是哈希表中的键,其值是对象命名空间中的配置参数。
  3. 处理这些对象集合的第三个类,我可以在其中定义 add 方法。这就是我遇到问题的地方。
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)
}
}
}

问题

我希望类Collection_Configs扩展内置集合类型并重写 Add 方法。就像通用 List<> 类型一样,我可以简单地输出引用我的集合的变量并自动返回集合。这样,我就不需要在 List 属性中点缀即可访问集合。事实上,我根本不需要 List 属性。

但是,当我从 System.Array 继承时,我需要在构造函数中提供固定的数组大小。我想避免这种情况,因为我的集合应该是可变的。我尝试从 List 继承,但我无法让语法工作;PowerShell 引发"找不到类型"错误。

有没有办法做到这一点?

更新

在mklement的有用答案之后,我将最后一堂课修改为:

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)
}
}
}

这似乎有效。除了继承之外,还必须对我如何点入输入类型进行其他更正,并且我还需要在其他 2 个类之后单独加载集合类,并在我的 else 语句中使用基类的 add 方法。

接下来,我将不得不进行一些其他验证以确保输入model_config类型。目前自定义集合接受任何输入,即使我自动将 add 参数转换为model_config,例如,

$config = [model_config]::new('a','b','c',@{'h'='t'})
$collection = [Collection_Configs]::new()
$collection.Add($config)

有效,但是

$collection.Add('test')

当它应该失败验证时也有效。也许它没有正确覆盖并使用基类的重载?

上次更新

现在一切似乎都在工作。该类的最后一次更新是:

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)
}
}
}

请注意,在else语句中,....GetMethod('Add')...对于Windows PowerShell是必需的,正如mklement0的超级有用和正确答案的脚注中所指出的那样。如果你能够使用 Core,那么 mklement0 的语法就可以了(我测试过)。

mklement0 也提到,这些类型需要单独加载。仅供参考,这可以在命令行上完成,以便通过键入 model_namespace 和 model_config 类并在对Collection_Configs执行相同操作之前按 Enter 键进行快速临时测试。

总之,这将在 PowerShell 中使用自定义方法创建自定义集合类型。

可以子类System.Collections.Generic.List`1,正如这个简化的例子,它派生自一个包含[regex]元素的列表,演示:[1]

using namespace System.Collections.Generic
# Subclass System.Collections.Generic.List`1 with [regex] elements.
class Collection_Configs : List[regex] {
# Override the .Add() method.
# Note: You probably want to override .AddRange() too.
Add([regex] $item) {
Write-Verbose -Verbose 'Doing custom things...'
# Call the base-class method.
([List[regex]] $this).Add($item)
}
}
# Sample use.
$list = [Collection_Configs]::new()
$list.Add([regex] 'foo')
$list

但是,如您所注意的,建议从基类System.Collections.ObjectModel.Collection`1派生自定义集合

using namespace System.Collections.ObjectModel
# Subclass System.Collections.ObjectModel`1 with [regex] elements.
class Collection_Configs : Collection[regex] {
# Override the .Add() method.
# Note: Unlike with List`1, there is no .AddRange() method.
Add([regex] $item) {
Write-Verbose -Verbose 'Doing custom things...'
# Call the base-class method.
([Collection[regex]] $this).Add($item)
}
}

至于优缺点:

  • List`1ObjectModel`1有更多的内置功能(方法),如.Reverse()Exists().ForEach()

  • .ForEach()的情况下,这实际上有利于ObjectModel`1:没有这样的方法可以避免与PowerShell的固有.ForEach()方法发生冲突。

请注意,无论哪种情况,都必须使用集合应由该类型组成的特定类型作为基类的泛型类型参数[regex]在上面的示例中,[Model_Config]在实际代码中(请参阅下一节)。

  • 如果改用[object],则集合不是类型安全的,因为它将具有一个void Add(object item)方法,每当使用非所需类型(或无法转换为该类型)的类型实例调用.Add()方法时,PowerShell 都会选择该方法。

但是,在您的案例中还有一个额外的挑战

  • 从 PowerShell 7.3.1 开始,由于确定列表元素类型的泛型类型参数另一个自定义class,因此必须事先在单独的脚本中意外加载定义依赖类Collection_Configs脚本

    • 这个要求是不幸的,至少在概念上与一般(同样不幸)的需求相关,以确保在执行封闭脚本之前加载class定义中引用的 .NET 类型 - 请参阅这篇文章,其接受的答案演示了解决方法。

    • 但是,鉴于在您的案例中涉及的所有类都是同一脚本文件的一部分,潜在的修复应该比链接帖子中讨论的更简单 - 请参阅 GitHub 问题 #18872


[1] 注意:Windows PowerShell中似乎存在一个错误,如果泛型类型参数(元素类型)恰好[pscustomobject]又名[psobject],则调用基类的.Add()方法将失败: 也就是说,虽然([List[pscustomobject]] $this).Add($item)在PowerShell(核心)7+中按预期工作,但Windows PowerShell中会发生错误,这需要以下基于反射的解决方法[List[pscustomobject]].GetMethod('Add').Invoke($this, [object[]] $item)

原始代码存在一些问题:

Using 关键字拼写错误。它应该使用。 Collection_Configs类中的$List变量未使用类型声明。它应该是[列表[Model_Config]]$List。 Collection_Configs类中的 Add 方法缺少其返回类型。它应该是 [无效] 添加 ([Model_Config]$newConfig)。 Add 方法缺少其左大括号。

最新更新