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