微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如果已存在,则防止将 pscustomobject 添加到数组

如何解决如果已存在,则防止将 pscustomobject 添加到数组

我觉得我无法弄清楚这一点很愚蠢,但是说我有一个包含 pscustomobjects 的数组。举一个令人难以置信的高层次的例子:

$arr = @()
$obj1 = [pscustomobject]@{prop1="bob";prop2="dude";prop3="awesome"}
$obj2 = [pscustomobject]@{prop1="bob";prop2="dude";prop3="awesome"}

$arr += $obj1

在这种情况下,$obj1 和 $obj2 具有完全相同的项目/属性。如何测试 $testarr 是否包含 $obj1 的属性以避免将 $obj2 添加到其中?

注意,上面是一个粗略的例子。 pscustomobjects 正在从数据集动态创建并添加到数组中,但我想避免添加重复项。

我了解以下返回 true,但我完全希望任何给定的单个属性都有重复项。因此,我需要将整个 pscustomobject 和所有属性一起比较以获得唯一性。

$arr.Name -Contains 'Bob' #returns true

附带问题... 为什么 $obj1 和 $obj2 本身不被视为相等?我认为这是因为它们在技术上是不同的对象,只是具有相同的值,但我不明白为什么会这样,但是两个不同的变量只是一个字符串测试相同。

$obj1 -eq $obj2  #returns false
$str1 = "test"
$str2 = "test"
$str1 -eq $str2  #returns true

解决方法

问题是开放式 [pscustomobject] 类型在相等比较和作为哈希表键方面的行为:

[pscustomobject] 是 .NET reference 类型(不定义 custom 相等比较),因此使用 -eq 测试比较两个实例引用相等,这意味着只有引用非常相同的实例的值才被认为是相等的。[1]

使用 [pscustomobject] 实例作为 hashtable 的键同样没有帮助,因为正如 iRon 指出的那样,在 .GetHashCode() 实例上调用 [pscustomobject] 总是产生相同的值,而不管实例的属性和值集如何。[2]可以说,这是一个bug,如{ {3}}。


解决方案

  • 如果您愿意使用 (PSv5+) GitHub issue #15806 代替 [pscustomobject] 实例,custom classes 提供了一个依赖于自定义 class 实现的解决方案System.IEquatable<T> interface 以支持自定义的、特定于类的相等性测试 - 但请注意,由于自定义类比较特定的硬编码属性,因此它不是 general 替换开放式 [pscustomobject] 类型,其实例可以具有任意属性集。

  • Santiago Squarzon's helpful answer 通过自定义 class 提供通用解决方案,包装一个哈希表并使用 XML 序列化 形式的 [pscustomobject] 条目作为条目键(使用 PowerShell 用于其远程处理和后台作业基础结构的序列化格式),依赖于具有相同内容的不同字符串这一事实。 em> 通过 .GetHahCode() 报告相同的哈希码。 这可能是最好的整体解决方案,因为它表现得相当好,同时提供了相当稳健的通用比较:它对 value 类型的属性值(正如典型的在 [pscustomobject] 实例中)并测试 reference 类型值的属性以确保值相等,但序列化深度的必要限制意味着它至少可以用于具有不同属性值的深度嵌套对象低于被视为相同的序列化深度 - 有关 PowerShell 的序列化及其限制的详细信息,请参阅 iRon's helpful answer

  • 以下是基于 iRon 答案的临时解决方案,不需要定义自定义 class es,但效果不佳强>.

# Available in PSv5+,to allow referencing the [System.Management.Automation.PSSerializer] type
# as just [PSSerializer]; in v4-,use the full type name.
using namespace System.Management.Automation

# Define a *list* rather than an array,because it is
# efficiently extensible
$list = [System.Collections.ArrayList] (
  [pscustomobject] @{prop1="bob";  prop2="dude";   prop3="awesome"},[pscustomobject] @{prop1="alice";prop2="dudette";prop3="awesome"}
)

# Conditionally add two objects to the list:
# One of them is a duplicate and will be ignored.
[pscustomobject]@{prop1="bob";prop2="dude";prop3="awesome"},[pscustomobject]@{prop1="ted";prop2="dude";prop3="middling"} | ForEach-Object {
  if ($list.ForEach({ [PSSerializer]::Serialize($_) }) -cnotcontains [PSSerializer]::Serialize($_)) {
     $null = $list.Add($_)
  }
}

请注意 this answer 的使用,以便(相对)有效地序列化列表 $list 的每个元素,但请注意,它总是涉及创建一个 临时 数组相同的大小,包含特定于元素的序列化。

有多种方法可以优化此代码的性能,但如果需要,您不妨使用 iRon 的解决方案。


[1] 例如,[pscustomobject] @{ foo=1 } -eq [pscustomobject] @{ foo=1 } 产生 $false,因为正在比较两个不同的实例;它们碰巧具有相同的属性和值集是无关紧要的。

[2] 例如,尽管提供了两个明显不同的对象作为输入,但下面的代码会打印两次相同的值: [pscustomobject] @{ foo=1 },[pscustomobject] @{ bar=2 } | % GetHashCode

[3] 例如,([pscustomobject]@{prop1="bob";prop2="dude";prop3="awesome"}).psbase.ToString() 逐字返回 @{prop1=bob; prop2=dude; prop3=awesome}

,

纯粹从 MS Docs 说起,我远非课堂专家。

要创建类似的类,您需要在类中实现 System.IEquatable<T>

class customObject : System.IEquatable[Object] {
    [string]$prop1
    [string]$prop2
    [string]$prop3

    [bool]Equals([Object]$obj) {
        return $this.prop1 -eq $obj.prop1 -and
               $this.prop2 -eq $obj.prop2 -and
               $this.prop3 -eq $obj.prop3
    }
}

$x = [customobject]@{prop1="bob";prop2="dude";prop3="awesome"}
$y = [customobject]@{prop1="bob";prop2="dude";prop3="awesome"}

$i = [customobject]@{prop1="john";prop2="dude";prop3="awesome"}
$z = [customobject]@{prop2="bob"}

$x -eq $y     # -> True
$x.Equals($y) # -> True
$x -eq $z     # -> False
$x -eq $i     # -> False
,

为了补充 @Santiago Squarzon@mklement0 的答案并努力实现您的最终目标“将唯一的 pscustomobject 添加到列表”:

Try to avoid using the increase assignment operator (+=) to create a collection
相反,建议使用哈希表 (binary search) 来检查重复对象:

class PSHashSet {
    $Dictionary = [System.Collections.Specialized.OrderedDictionary]::new([StringComparer]::OrdinalIgnoreCase)
    [Void]Add([pscustomobject]$Item) {
        $Key = [System.Management.Automation.PSSerializer]::Serialize($Item)
        $This.Dictionary[$Key] = $Item
    }
    [pscustomobject[]]Get() {
        Return $This.Dictionary.Values
    }
}

用法:

$Arr = [PSHashSet]::New()

$Arr.Add([pscustomobject]@{prop1="bob";prop2="dude";prop3="awesome"})
$Arr.Add([pscustomobject]@{prop1="bob";prop2="dude";prop3="awesome"})
$Arr.Add([pscustomobject]@{prop1="john";prop2="dude";prop3="awesome"})

$Arr.Get()

prop1 prop2 prop3
----- ----- -----
bob   dude  awesome
john  dude  awesome

注意:
添加具有未对齐属性的对象时要小心,因为它们可能不会显示在显示器上(尽管它们确实存在于列表中),请参阅:Not all properties displayed

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。