创建对哈希表元素的引用

时间:2018-05-01 23:40:35

标签: powershell reference hashtable

所以,我正在尝试创建一个可用于数据导航的树型变量。尝试在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似乎处理不同。

3 个答案:

答案 0 :(得分:5)

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

在固定大小的数组上使用+=时,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 :(得分:2)

complement Mathias R. Jessen's helpful answer

  • 的确,任何数组属于固定大小,无法扩展 ({ {1}}创建一个空的@()数组。)

  • PowerShell中的
  • [object[]]悄悄地创建一个 new 数组,其中包含所有原始元素和新元素的副本,以及将 分配给LHS。

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

有关$Tree.Children的适当用法的讨论,请参见下一节。

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

因为[ref]创建了一个新的数组,但是,LHS上的任何内容 - 无论是+=还是$Pointer.Value,只有[ref] - 只是接收数组的引用,而$Pointer仍然指向旧数组。

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

$Tree.Children

请注意,PS> [object]::ReferenceEquals($Pointer.Value, $Tree.Children) False 仅适用于reference types,而不适用于value types - 包含后者商店值的变量直接而非引用数据存储在其他地方

的Mathias'方法通过使用[List`1]实例而不是数组来解决您的问题,该数组可以使用[object]::ReferenceEquals()方法进行扩展,以便存储在.Add()中的引用永远不需要改变并继续引用与$Pointer[.Value]相同的列表。

关于您的后续问题:适当使用$Tree.Children

[ref]

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

$Tree2

另请注意,更典型的PS> $Tree2 = 'Now I am a string.'; $Pointer.Value Now I am a string. 用例是将变量作为 by-reference 参数传递给函数。

[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 静态指的是最初< / em>存储在$Pointer.Value

$Tree2.prop

PowerShell应该阻止阻止使用非变量的任何内容 - 请参阅this GitHub issue

答案 2 :(得分:0)

您可以在树结构中创建指针:

$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