【问题标题】:Creating a reference to a hash table element创建对哈希表元素的引用
【发布时间】:2018-10-12 00:15:00
【问题描述】:

所以,我正在尝试创建一个可用于数据导航的树型变量。我在尝试在 PowerShell 中的哈希表上使用引用变量时遇到了一个问题。考虑以下代码:

$Tree = @{ TextValue = "main"; Children = @() }
$Item = @{ TextValue = "sub"; Children = @() }
$Pointer = [ref] $Tree.Children
$Pointer.Value += $Item
$Tree

在检查引用变量$Pointer时,它会显示适当的值,但主变量$Tree不受影响。有没有办法在PowerShell中创建对哈希表元素的引用,我必须切换到二维数组?

编辑更多信息:

我接受了 Mathias 的回答,因为使用 List 看起来正是我所需要的,但需要更清楚地了解数组和引用的交互方式。试试这个代码:

$Tree1 = @()
$Pointer = $Tree1
$Pointer += 1
Write-Host "tree1 is " $Tree1
$Tree2 = @()
$Pointer = [ref] $Tree2
$Pointer.Value += 1
Write-Host "tree2 is " $Tree2

从输出中可以看出,可以获取对数组的引用,然后通过该引用修改数组的大小。我认为如果一个数组是另一个数组或哈希表的元素,它也可以工作,但事实并非如此。 PowerShell 似乎以不同的方式处理这些问题。

【问题讨论】:

    标签: powershell reference hashtable


    【解决方案1】:

    我怀疑这是 += 在数组上工作方式的不幸副作用。

    当您在固定大小的数组上使用 += 时,PowerShell 会将原始数组替换为新的(和更大的)数组。我们可以验证$Pointer.Value 不再引用与GetHashCode() 相同的数组:

    PS C:\> $Tree = @{ Children = @() }
    PS C:\> $Pointer = [ref]$Tree.Children
    PS C:\> $Tree.Children.GetHashCode() -eq $Pointer.Value.GetHashCode()
    True
    PS C:\> $Pointer.Value += "Anything"
    PS C:\> $Tree.Children.GetHashCode() -eq $Pointer.Value.GetHashCode()
    False
    

    解决此问题的一种方法是避免使用@()+=

    您可以改用List 类型:

    $Tree = @{ TextValue = "main"; Children = New-Object System.Collections.Generic.List[psobject] }
    $Item = @{ TextValue = "sub"; Children = New-Object System.Collections.Generic.List[psobject] }
    $Pointer = [ref] $Tree.Children
    $Pointer.Value.Add($Item)
    $Tree
    

    【讨论】:

    • 确实(+1);测试两个变量/表达式是否引用引用类型的同一实例的更直接方法:[object]::ReferenceEquals($Tree.Children, $Pointer.Value);我建议使用 [object] 而不是 [psobject] - 最好将后者视为不可见的辅助类型(并且仅在需要临时构建自定义对象时通过其 [pscustomobject] 别名使用它)。
    • 抽象有时很有帮助。不过,数组上没有Add 方法吗?
    • 感谢您的回答,我想我会试试List 看看效果如何。
    • @TheIncorrigible1: 是的,由于实现了IList 接口,数组有一个.Add() 方法,但它没用并且总是以Collection was of a fixed size. 失败
    【解决方案2】:

    complement Mathias R. Jessen's helpful answer

    • 确实,任何数组都是固定大小并且不能就地扩展@()创建一个空的[object[]] 数组)。

    • += 在 PowerShell 中悄悄创建一个 数组,其中包含所有原始元素和新元素的副本,并分配 到 LHS。


    您对[ref] 的使用毫无意义,因为仅$Pointer = $Tree.Children 就足以将引用复制到存储在$Tree.Children 中的数组中。

    关于[ref]的适当使用的讨论见底部。

    因此,both $Tree.Children$Pointer 将包含对同一数组的引用,就像 $Pointer.Value 在基于 [ref] 的方法中所做的那样。

    因为+= 创建了一个 数组,但是,无论LHS 上的什么——无论是$Pointer.Value 还是没有[ref],只有$Pointer——只接收一个 new 引用 new 数组,而 $Tree.Children 仍然指向旧数组。

    您可以通过使用直接确定两个变量或表达式是否“指向”引用类型(所有集合都是)的同一实例来验证这一点:

    PS> [object]::ReferenceEquals($Pointer.Value, $Tree.Children)
    False
    

    注意[object]::ReferenceEquals() 仅适用于reference types,不适用于value types - 包含后者的变量直接 存储值而不是引用 存储的数据其他地方


    Mathias 的方法通过使用 [List`1] 实例而不是数组来解决您的问题,数组可以通过其 .Add() 方法就地扩展,以便将引用存储在 $Pointer[.Value]永远不需要更改并继续引用与 $Tree.Children 相同的列表。


    关于您的后续问题:[ref] 的适当使用

    $Tree2 = @()
    $Pointer = [ref] $Tree2
    

    在这种情况下,因为[ref] 应用于变量 - 按照设计 - 它创建了一个有效的变量别名:@987654351 @ 保持指向 $Tree2 包含的任何内容,即使稍后将不同的数据分配给 $Tree2(无论该数据是值类型还是引用类型实例):

    PS> $Tree2 = 'Now I am a string.'; $Pointer.Value
    Now I am a string.
    

    还要注意更典型的[ref] 用例是将变量作为通过引用参数传递给函数。

    PS> function foo { param([ref] $vRef) ++$vRef.Value }; $v=1; foo ([ref] $v); $v
    2  # value of $v was incremented via $vRef.Value
    

    相比之下,您不能使用[ref] 来创建对数据 的持久间接引用,例如属性 对象包含 在一个变量中,并且使用[ref] 基本上没有意义:

    $Tree2 = @{ prop = 'initial val' }
    $Pointer = [ref] $Tree2.prop   # [ref] is pointless here
    

    后来更改$Tree2.prop不会反映在$Pointer.Value中,因为$Pointer.Value静态指的是原来存储在@中的引用987654363@:

    PS> $Tree2.prop = 'later val'; $Pointer.Value
    initial val   # $Pointer.Value still points to the *original* data
    

    PowerShell 应该防止[ref] 用于任何非变量。然而,[ref] 有一个合法的——尽管是异国情调的——“标签外”使用,用于促进在调用者的范围内更新值从后代范围,如概念性about_Ref 帮助主题中所示.

    【讨论】:

      【解决方案3】:

      您可以创建指向树结构的指针:

      $Tree = @{ TextValue = "main"; Children = [ref]@() }
      $Item1 = @{ TextValue = "sub1"; Children = [ref]@() }
      $Item2 = @{ TextValue = "sub2"; Children = [ref]@() }
      $Item3 = @{ TextValue = "subsub"; Children = [ref]@() }
      $Pointer = $Tree.Children
      $Pointer.Value += $Item1
      $Pointer.Value += $Item2
      $Pointer.Value.Get(0).Children.Value += $Item3
      
      function Show-Tree {
        param ( [hashtable] $Tree )
      
        Write-Host $Tree.TextValue
        if ($Tree.Children.Value.Count -ne 0) {
          $Tree.Children.Value | ForEach-Object { Show-Tree $_ }
        }
      }
      
      Show-Tree $Tree
      

      输出:

      main
      sub1
      subsub
      sub2
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-01-01
        • 2021-10-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-12-20
        • 2012-05-30
        • 1970-01-01
        相关资源
        最近更新 更多