创建对散列 table 元素的引用
Creating a reference to a hash table element
因此,我正在尝试创建一个可用于数据导航的树型变量。我在 PowerShell 中尝试对散列 tables 使用引用变量时遇到了一个问题 运行。考虑以下代码:
$Tree = @{ TextValue = "main"; Children = @() }
$Item = @{ TextValue = "sub"; Children = @() }
$Pointer = [ref] $Tree.Children
$Pointer.Value += $Item
$Tree
检查参考变量$Pointer
时,它显示适当的值,但主变量$Tree
不受影响。没有办法在 PowerShell 中创建对散列 table 元素的引用,我必须切换到二维数组吗?
编辑更多信息:
我接受了 Mathias 的回答,因为使用 List
看起来正是我所需要的,但数组和引用的交互方式需要更清楚一些。试试这个代码:
$Tree1 = @()
$Pointer = $Tree1
$Pointer += 1
Write-Host "tree1 is " $Tree1
$Tree2 = @()
$Pointer = [ref] $Tree2
$Pointer.Value += 1
Write-Host "tree2 is " $Tree2
从输出中可以看出,可以获取对数组的引用,然后通过该引用修改数组的大小。我认为如果一个数组是另一个数组的元素或散列 table,它也可以工作,但事实并非如此。 PowerShell 似乎以不同的方式处理这些问题。
我怀疑这是 +=
处理数组的方式的不幸副作用。
当您在固定大小的数组上使用 +=
时,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
至complement Mathias R. Jessen's helpful answer:
确实,任何数组都是固定大小,不能扩展就地(@()
创建一个空的[object[]]
数组)。
+=
在 PowerShell 中悄悄创建一个 new 数组,其中包含所有原始数据的副本元素加上新元素,并将 that 分配给 LHS。
您使用 [ref]
是没有意义的,因为仅 $Pointer = $Tree.Children
就足以将 reference 复制到存储在 $Tree.Children
.
中的数组
有关 [ref]
.
的适当用法的讨论,请参见底部部分
因此, $Tree.Children
和 $Pointer
将包含对同一数组的引用,就像 $Pointer.Value
在您的 [ref]
基于方法。
因为 +=
创建了一个 new 数组,但是,无论 LHS 上有什么 - 无论是 $Pointer.Value
还是没有 [ref]
, just $Pointer
- 只是接收到 new 对 new 数组的引用,而 $Tree.Children
仍然指向旧数组。
您可以通过使用确定两个变量或表达式是否“指向”引用类型[=139]的同一实例的直接方法来验证这一点=](所有集合都是):
PS> [object]::ReferenceEquals($Pointer.Value, $Tree.Children)
False
注意[object]::ReferenceEquals()
只适用于reference types, not value types——包含后者存储值的变量直接而不是引用[=136] =] 数据存储在 别处 .
Mathias 的方法通过使用 [List`1]
实例而不是数组来解决您的问题,可以使用其 .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.
另请注意,典型的 [ref]
用例是将变量传递给函数 .NET API 方法 具有 ref
或 out
参数;虽然您 可以 将它与 PowerShell 脚本和函数一起使用,以便传递 by-reference 参数,如下例所示,这是最好的避免:
# Works, but best avoided in PowerShell code.
PS> function foo { param([ref] $vRef) ++$vRef.Value }; $v=1; foo ([ref] $v); $v
2 # value of $v was incremented via $vRef.Value
相比之下,您不能使用 [ref]
创建对 data 的这种持久性 间接 引用,比如属性一个对象包含在一个变量中,以及[ref]
的使用在那里基本上毫无意义:
$Tree2 = @{ prop = 'initial val' }
$Pointer = [ref] $Tree2.prop # [ref] is pointless here
后面改$Tree2.prop
是不是体现在$Pointer.Value
,因为$Pointer.Value
静态引用参考 最初 存储在 $Tree2.prop
:
PS> $Tree2.prop = 'later val'; $Pointer.Value
initial val # $Pointer.Value still points to the *original* data
PowerShell 应该防止将[ref]
用于任何不是变量的东西。然而,[ref]
有一个合法的——尽管是异国情调的——“标签外”使用,用于促进调用者范围 从后代范围 更新值,如概念中所示about_Ref 帮助主题。
您可以在树结构中创建指针:
$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
因此,我正在尝试创建一个可用于数据导航的树型变量。我在 PowerShell 中尝试对散列 tables 使用引用变量时遇到了一个问题 运行。考虑以下代码:
$Tree = @{ TextValue = "main"; Children = @() }
$Item = @{ TextValue = "sub"; Children = @() }
$Pointer = [ref] $Tree.Children
$Pointer.Value += $Item
$Tree
检查参考变量$Pointer
时,它显示适当的值,但主变量$Tree
不受影响。没有办法在 PowerShell 中创建对散列 table 元素的引用,我必须切换到二维数组吗?
编辑更多信息:
我接受了 Mathias 的回答,因为使用 List
看起来正是我所需要的,但数组和引用的交互方式需要更清楚一些。试试这个代码:
$Tree1 = @()
$Pointer = $Tree1
$Pointer += 1
Write-Host "tree1 is " $Tree1
$Tree2 = @()
$Pointer = [ref] $Tree2
$Pointer.Value += 1
Write-Host "tree2 is " $Tree2
从输出中可以看出,可以获取对数组的引用,然后通过该引用修改数组的大小。我认为如果一个数组是另一个数组的元素或散列 table,它也可以工作,但事实并非如此。 PowerShell 似乎以不同的方式处理这些问题。
我怀疑这是 +=
处理数组的方式的不幸副作用。
当您在固定大小的数组上使用 +=
时,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
至complement Mathias R. Jessen's helpful answer:
确实,任何数组都是固定大小,不能扩展就地(
@()
创建一个空的[object[]]
数组)。+=
在 PowerShell 中悄悄创建一个 new 数组,其中包含所有原始数据的副本元素加上新元素,并将 that 分配给 LHS。
您使用 [ref]
是没有意义的,因为仅 $Pointer = $Tree.Children
就足以将 reference 复制到存储在 $Tree.Children
.
有关 [ref]
.
因此, $Tree.Children
和 $Pointer
将包含对同一数组的引用,就像 $Pointer.Value
在您的 [ref]
基于方法。
因为 +=
创建了一个 new 数组,但是,无论 LHS 上有什么 - 无论是 $Pointer.Value
还是没有 [ref]
, just $Pointer
- 只是接收到 new 对 new 数组的引用,而 $Tree.Children
仍然指向旧数组。
您可以通过使用确定两个变量或表达式是否“指向”引用类型[=139]的同一实例的直接方法来验证这一点=](所有集合都是):
PS> [object]::ReferenceEquals($Pointer.Value, $Tree.Children)
False
注意[object]::ReferenceEquals()
只适用于reference types, not value types——包含后者存储值的变量直接而不是引用[=136] =] 数据存储在 别处 .
Mathias 的方法通过使用 [List`1]
实例而不是数组来解决您的问题,可以使用其 .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.
另请注意,典型的 [ref]
用例是将变量传递给函数 .NET API 方法 具有 ref
或 out
参数;虽然您 可以 将它与 PowerShell 脚本和函数一起使用,以便传递 by-reference 参数,如下例所示,这是最好的避免:
# Works, but best avoided in PowerShell code.
PS> function foo { param([ref] $vRef) ++$vRef.Value }; $v=1; foo ([ref] $v); $v
2 # value of $v was incremented via $vRef.Value
相比之下,您不能使用 [ref]
创建对 data 的这种持久性 间接 引用,比如属性一个对象包含在一个变量中,以及[ref]
的使用在那里基本上毫无意义:
$Tree2 = @{ prop = 'initial val' }
$Pointer = [ref] $Tree2.prop # [ref] is pointless here
后面改$Tree2.prop
是不是体现在$Pointer.Value
,因为$Pointer.Value
静态引用参考 最初 存储在 $Tree2.prop
:
PS> $Tree2.prop = 'later val'; $Pointer.Value
initial val # $Pointer.Value still points to the *original* data
PowerShell 应该防止将[ref]
用于任何不是变量的东西。然而,[ref]
有一个合法的——尽管是异国情调的——“标签外”使用,用于促进调用者范围 从后代范围 更新值,如概念中所示about_Ref 帮助主题。
您可以在树结构中创建指针:
$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