Powershell 参数引用
Powershell arguments by reference
我的理解是,像散列 table 这样的复杂对象总是通过引用传递,而像字符串或布尔值这样的简单对象则通过值传递。我一直在使用这个“事实”来对函数进行依赖注入,它似乎一直有效,直到我需要将依赖传递给另一个函数。
所以我决定做一个简化的测试,看看哪里出了问题。现在甚至依赖注入似乎也不起作用。在这段代码中,我的期望是,因为我最初将 $state
定义为 Primary 在它的“初始化”模式下的 return 值,所以我可以简单地通过引用将其传递给其他函数,并且在查看时在 $state
完成时我会看到所有四次,以及 id。
function Primary {
param (
[parameter(Mandatory=$true,
ParameterSetName = 'initialize')]
[String]$id,
[parameter(Mandatory=$true,
ParameterSetName = 'process')]
[Hashtable]$state
)
if ($id) {
Write-Host 'initialize Primary'
$primary = [Ordered]@{}
$primary.Add('id', $id)
$primary.Add('primaryInit', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
Secondary -state:$primary -init
} else {
Write-Host 'process Primary'
$primary = $true
$state.Add('primaryProcess', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
Secondary -state:$state
}
return $primary
}
function Secondary {
param (
[parameter(ParameterSetName = 'initialize')]
[Switch]$init,
[parameter(Mandatory=$true)]
[Hashtable]$state
)
if ($init) {
Write-Host 'initialize Secondary'
$state.Add('secondaryInit', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
} else {
Write-Host 'process Secondary'
$state.Add('secondaryProcess', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
}
}
CLS
$state = Primary -id:'Test'
Primary -state:$state
CLS
foreach ($key in $state.Keys) {
Write-Host "$key $($state.$key)"
}
Write-Host
没有这样的运气,我只看到了primaryInit时间。然而,在我更复杂的程序中,似乎我正在通过引用传递 $state
。所以,我想知道这个非常简单的例子有什么不同,它没有按预期运行?还是我误解了正在发生的事情,并且在我的生产代码中我正在创建一种我误解为先天行为的行为?
我还尝试了一个更简化的版本,从函数中删除调用函数的部分。
function ByReference {
param (
[Hashtable]$state
)
$state.Add('now', (Get-Date))
}
$state = [Ordered]@{
id = 'test'
}
ByReference $state
foreach ($key in $state.Keys) {
Write-Host "$key $($state.$key)"
}
这也表明 $state
被 Value 传递,因此在查看 Main 中的变量时看不到变化。
编辑:基于@Daniel 的link,我修改为
function Primary {
param (
[parameter(Mandatory=$true,
ParameterSetName = 'initialize')]
[String]$id,
[parameter(Mandatory=$true,
ParameterSetName = 'process')]
[Ref]$state
)
if ($id) {
Write-Host 'initialize Primary'
$primary = [Ordered]@{}
$primary.Add('id', $id)
$primary.Add('primaryInit', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
Secondary -state:$primary -init
} else {
Write-Host 'process Primary'
$primary = $state
$state.Add('primaryProcess', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
Secondary -state:$state
}
return $primary
}
function Secondary {
param (
[parameter(ParameterSetName = 'initialize')]
[Switch]$init,
[parameter(Mandatory=$true)]
[Ref]$state
)
if ($init) {
Write-Host 'initialize Secondary'
$state.Add('secondaryInit', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
} else {
Write-Host 'process Secondary'
$state.Add('secondaryProcess', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
}
}
CLS
$state = Primary -id:'Test'
Primary -state:$state
CLS
foreach ($key in $state.Keys) {
Write-Host "$key $($state.$key)"
}
Write-Host
除了第一次,这仍然没有显示任何内容。也就是说,我可以让它工作。
Function Test($data)
{
$data.Test = "New Text"
}
$var = @{}
Test -data $var
$var
这让我想到也许这只在您不使用 param()
块时才有效。所以我尝试删除参数块并使用 function Primary ([String]$id, [Ref]$state) {}
。还是不开心。
我注意到的另一件事是所有示例都在 main 中创建变量。我在方法的初始化模式下创建变量。难道变量需要是全局或脚本范围?我在最初定义 $primary 时尝试使用范围修饰符,但 thiat 也不起作用。
编辑 2:看来关键是您不能键入 byRef 参数。
所以这有效。
function Test-Primary {
param (
[String]$id,
$state = [Ordered]@{}
)
if ($id) {
Write-Host 'initialize Primary'
$state.Add('id', $id)
$state.Add('primaryInit', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
Test-Secondary -state:$state -init
return $state
} else {
Write-Host 'process Primary'
$state.Add('primaryProcess', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
Test-Secondary -state:$state
}
}
function Test-Secondary {
param (
[Switch]$init,
$state
)
if ($init) {
Write-Host 'initialize Secondary'
$state.Add('secondaryInit', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
} else {
Write-Host 'process Secondary'
$state.Add('secondaryProcess', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
}
}
CLS
$state = Test-Primary -id:'Test'
Test-Primary -state:$state
CLS
foreach ($key in $state.Keys) {
Write-Host "$key $($state.$key)"
}
Write-Host
有点奇怪,因为我希望您可以专门键入参数,因此请确保您不更改类型,但我想这只是开始使用 类 的另一个原因。函数是“草率的”,因为我猜它们是为肮脏的东西设计的。
编辑:好的,进步了,但似乎我仍在尝试做一些奇怪的事情。我得出的结论是,在执行这些多“模式”函数时,其中一种模式 return 是一个值,而另一种模式使用按引用传递的变量,您需要为 return 起一个不同的名称价值。除此之外,我在调用函数和在函数中声明变量时都需要 [Ref]
。然后我假设我可以使用 [Ref]
甚至散列 table,以此来提醒自己这是一个参考值。然后 .Value
实际处理变量的需要让我想到了这个。
function Test-Primary {
param (
[String]$id,
[Ref]$state
)
if ($id) {
Write-Host 'initialize Primary'
$testPrimary = [Ordered]@{}
$testPrimary.Add('id', $id)
$testPrimary.Add('primaryInit', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
Test-Secondary -state:([Ref]$testPrimary) -init
return $testPrimary
} else {
Write-Host 'process Primary'
$state.Value.Add('primaryProcess', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
Test-Secondary -state:$state
}
}
function Test-Secondary {
param (
[Switch]$init,
[Ref]$state
)
if ($init) {
Write-Host 'initialize Secondary'
$state.Value.Add('secondaryInit', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
} else {
Write-Host 'process Secondary'
$state.Value.Add('secondaryProcess', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
}
}
CLS
$state = Test-Primary -id:'Test'
Test-Primary -state:([Ref]$state)
CLS
foreach ($key in $state.Keys) {
Write-Host "$key $($state.$key)"
}
Write-Host
如您所知,哈希表不需要通过引用传递来更改其属性。但要向您展示 [Ref]
的实际工作原理,请看以下示例:
function Test-RefParameter
{
[CmdletBinding()]
param
(
[Parameter(Mandatory = $true)]
[Ref] $Output
)
$Output.Value = "output from test"
}
$testVar = "before test"
Write-Information "Current value: '$testVar'" -InformationAction Continue
Test-RefParameter -Output ([Ref]$testVar)
Write-Information "Current value: '$testVar'" -InformationAction Continue
其中显示:
Current value: 'before test'
Current value: 'output from test'
所以关键是在给[Ref]
参数赋值的时候使用.Value
我的理解是,像散列 table 这样的复杂对象总是通过引用传递,而像字符串或布尔值这样的简单对象则通过值传递。我一直在使用这个“事实”来对函数进行依赖注入,它似乎一直有效,直到我需要将依赖传递给另一个函数。
所以我决定做一个简化的测试,看看哪里出了问题。现在甚至依赖注入似乎也不起作用。在这段代码中,我的期望是,因为我最初将 $state
定义为 Primary 在它的“初始化”模式下的 return 值,所以我可以简单地通过引用将其传递给其他函数,并且在查看时在 $state
完成时我会看到所有四次,以及 id。
function Primary {
param (
[parameter(Mandatory=$true,
ParameterSetName = 'initialize')]
[String]$id,
[parameter(Mandatory=$true,
ParameterSetName = 'process')]
[Hashtable]$state
)
if ($id) {
Write-Host 'initialize Primary'
$primary = [Ordered]@{}
$primary.Add('id', $id)
$primary.Add('primaryInit', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
Secondary -state:$primary -init
} else {
Write-Host 'process Primary'
$primary = $true
$state.Add('primaryProcess', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
Secondary -state:$state
}
return $primary
}
function Secondary {
param (
[parameter(ParameterSetName = 'initialize')]
[Switch]$init,
[parameter(Mandatory=$true)]
[Hashtable]$state
)
if ($init) {
Write-Host 'initialize Secondary'
$state.Add('secondaryInit', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
} else {
Write-Host 'process Secondary'
$state.Add('secondaryProcess', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
}
}
CLS
$state = Primary -id:'Test'
Primary -state:$state
CLS
foreach ($key in $state.Keys) {
Write-Host "$key $($state.$key)"
}
Write-Host
没有这样的运气,我只看到了primaryInit时间。然而,在我更复杂的程序中,似乎我正在通过引用传递 $state
。所以,我想知道这个非常简单的例子有什么不同,它没有按预期运行?还是我误解了正在发生的事情,并且在我的生产代码中我正在创建一种我误解为先天行为的行为?
我还尝试了一个更简化的版本,从函数中删除调用函数的部分。
function ByReference {
param (
[Hashtable]$state
)
$state.Add('now', (Get-Date))
}
$state = [Ordered]@{
id = 'test'
}
ByReference $state
foreach ($key in $state.Keys) {
Write-Host "$key $($state.$key)"
}
这也表明 $state
被 Value 传递,因此在查看 Main 中的变量时看不到变化。
编辑:基于@Daniel 的link,我修改为
function Primary {
param (
[parameter(Mandatory=$true,
ParameterSetName = 'initialize')]
[String]$id,
[parameter(Mandatory=$true,
ParameterSetName = 'process')]
[Ref]$state
)
if ($id) {
Write-Host 'initialize Primary'
$primary = [Ordered]@{}
$primary.Add('id', $id)
$primary.Add('primaryInit', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
Secondary -state:$primary -init
} else {
Write-Host 'process Primary'
$primary = $state
$state.Add('primaryProcess', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
Secondary -state:$state
}
return $primary
}
function Secondary {
param (
[parameter(ParameterSetName = 'initialize')]
[Switch]$init,
[parameter(Mandatory=$true)]
[Ref]$state
)
if ($init) {
Write-Host 'initialize Secondary'
$state.Add('secondaryInit', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
} else {
Write-Host 'process Secondary'
$state.Add('secondaryProcess', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
}
}
CLS
$state = Primary -id:'Test'
Primary -state:$state
CLS
foreach ($key in $state.Keys) {
Write-Host "$key $($state.$key)"
}
Write-Host
除了第一次,这仍然没有显示任何内容。也就是说,我可以让它工作。
Function Test($data)
{
$data.Test = "New Text"
}
$var = @{}
Test -data $var
$var
这让我想到也许这只在您不使用 param()
块时才有效。所以我尝试删除参数块并使用 function Primary ([String]$id, [Ref]$state) {}
。还是不开心。
我注意到的另一件事是所有示例都在 main 中创建变量。我在方法的初始化模式下创建变量。难道变量需要是全局或脚本范围?我在最初定义 $primary 时尝试使用范围修饰符,但 thiat 也不起作用。
编辑 2:看来关键是您不能键入 byRef 参数。 所以这有效。
function Test-Primary {
param (
[String]$id,
$state = [Ordered]@{}
)
if ($id) {
Write-Host 'initialize Primary'
$state.Add('id', $id)
$state.Add('primaryInit', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
Test-Secondary -state:$state -init
return $state
} else {
Write-Host 'process Primary'
$state.Add('primaryProcess', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
Test-Secondary -state:$state
}
}
function Test-Secondary {
param (
[Switch]$init,
$state
)
if ($init) {
Write-Host 'initialize Secondary'
$state.Add('secondaryInit', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
} else {
Write-Host 'process Secondary'
$state.Add('secondaryProcess', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
}
}
CLS
$state = Test-Primary -id:'Test'
Test-Primary -state:$state
CLS
foreach ($key in $state.Keys) {
Write-Host "$key $($state.$key)"
}
Write-Host
有点奇怪,因为我希望您可以专门键入参数,因此请确保您不更改类型,但我想这只是开始使用 类 的另一个原因。函数是“草率的”,因为我猜它们是为肮脏的东西设计的。
编辑:好的,进步了,但似乎我仍在尝试做一些奇怪的事情。我得出的结论是,在执行这些多“模式”函数时,其中一种模式 return 是一个值,而另一种模式使用按引用传递的变量,您需要为 return 起一个不同的名称价值。除此之外,我在调用函数和在函数中声明变量时都需要 [Ref]
。然后我假设我可以使用 [Ref]
甚至散列 table,以此来提醒自己这是一个参考值。然后 .Value
实际处理变量的需要让我想到了这个。
function Test-Primary {
param (
[String]$id,
[Ref]$state
)
if ($id) {
Write-Host 'initialize Primary'
$testPrimary = [Ordered]@{}
$testPrimary.Add('id', $id)
$testPrimary.Add('primaryInit', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
Test-Secondary -state:([Ref]$testPrimary) -init
return $testPrimary
} else {
Write-Host 'process Primary'
$state.Value.Add('primaryProcess', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
Test-Secondary -state:$state
}
}
function Test-Secondary {
param (
[Switch]$init,
[Ref]$state
)
if ($init) {
Write-Host 'initialize Secondary'
$state.Value.Add('secondaryInit', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
} else {
Write-Host 'process Secondary'
$state.Value.Add('secondaryProcess', (Get-Date))
Start-Sleep -s:(Get-Random -Minimum 1 -Maximum 5)
}
}
CLS
$state = Test-Primary -id:'Test'
Test-Primary -state:([Ref]$state)
CLS
foreach ($key in $state.Keys) {
Write-Host "$key $($state.$key)"
}
Write-Host
如您所知,哈希表不需要通过引用传递来更改其属性。但要向您展示 [Ref]
的实际工作原理,请看以下示例:
function Test-RefParameter
{
[CmdletBinding()]
param
(
[Parameter(Mandatory = $true)]
[Ref] $Output
)
$Output.Value = "output from test"
}
$testVar = "before test"
Write-Information "Current value: '$testVar'" -InformationAction Continue
Test-RefParameter -Output ([Ref]$testVar)
Write-Information "Current value: '$testVar'" -InformationAction Continue
其中显示:
Current value: 'before test'
Current value: 'output from test'
所以关键是在给[Ref]
参数赋值的时候使用.Value