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