Why and how are these two $null values different? (3个答案)
3年前关闭。
语境
考虑以下辅助函数:
Filter If-Null(
[Parameter(ValueFromPipeline=$true)]$value,
[Parameter(Position=0)]$default
) {
Write-Verbose "If ($value) {$value} Else {$default}"
if ($value) {$value} else {$default}
}
从本质上讲,它是作为管道功能实现的
null-coalescing operator。它应该像这样工作:
PS> $myVar = $null
PS> $myVar | If-Null "myDefault" -Verbose
VERBOSE: If () {} Else {myDefault}
myDefault
但是,当我将
$myVar
设置为空数组中的第一个元素时...
PS> $myVar = @() | Select-Object -First 1
...应该实际上与
$null
相同...
PS> $myVar -eq $null
True
PS> -not $myVar
True
...然后管道不再起作用:
PS> $myVar | If-Null "myDefault" -Verbose
根本没有输出。甚至没有冗长的字样。这意味着
If-Null
甚至没有执行。
问题
所以看起来
@() | select -f 1
,尽管是
-eq
到
$null
,但是是某种程度上不同的
$null
,它以某种方式破坏了管道?
谁能解释这种行为?我想念什么?
附加信息
PS> (@() | select -f 1).GetType()
You cannot call a method on a null-valued expression.
At line:1 char:1
+ (@() | select -f 1).GetType()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
PS> (@() | select -f 1) | Get-Member
Get-Member : You must specify an object for the Get-Member cmdlet.
At line:1 char:23
+ (@() | select -f 1) | Get-Member
+ ~~~~~~~~~~
+ CategoryInfo : CloseError: (:) [Get-Member], InvalidOperationException
+ FullyQualifiedErrorId : NoObjectInGetMember,Microsoft.PowerShell.Commands.GetMemberCommand
PS> $PSVersionTable
Name Value
---- -----
PSVersion 5.0.10586.117
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.10586.117
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
Solution
Ansgar's explanation is correct (a better explanation can be found in mklement0's answer to the duplicate question). I just wanted to share my solution to the problem.
I fixed If-Null
such that it returns the $default
even when nothing is processed:
Function If-Null(
[Parameter(ValueFromPipeline = $true)]$value,
[Parameter(Position = 0)]$default
) {
Process {
$processedSomething = $true
If ($value) { $value } Else { $default }
}
# This makes sure the $default is returned even when the input was an empty array or of
# type [System.Management.Automation.Internal.AutomationNull]::Value (which prevents
# execution of the Process block).
End { If (-not $processedSomething) { $default }}
}
此版本现在可以正确处理空管道结果:
PS> @() | select -f 1 | If-Null myDefault
myDefault
数组通过管道展开,因此每个数组元素都分别传递。如果将空数组传递到管道中,则基本上不会展开任何内容,这意味着永远不会调用下游cmdlet,从而为您留下一个空变量。
您可以通过将$null
和@()
传递到仅回显每个输入项字符串的循环中来观察此行为:
PS C:\> @()| %{'foo'}#这里没有输出!
PS C:\> $ null | %{'foo'}#输出:foo
富
根据上下文,这与带有“value” $null
的变量不同。即使在大多数情况下,PowerShell会自动将“空”变量转换为$null
值(如您的检查所示),但在将变量传递到管道中时却不会这样做。在那种情况下,您仍然不会将任何内容传递到管道中,因此永远不会调用过滤器。