为什么以及这两个$ null值有何不同?

Dav*_*osh 10 powershell null automation-null

显然,在PowerShell(第3版)中并非所有$null都是相同的:

    >function emptyArray() { @() }
    >$l_t = @() ; $l_t.Count
0
    >$l_t1 = @(); $l_t1 -eq $null; $l_t1.count; $l_t1.gettype()
0
IsPublic IsSerial Name                                     BaseType                                                         
-------- -------- ----                                     --------                                                         
True     True     Object[]                                 System.Array                                                     
    >$l_t += $l_t1; $l_t.Count
0
    >$l_t += emptyArray; $l_t.Count
0
    >$l_t2 = emptyArray; $l_t2 -eq $null; $l_t2.Count; $l_t2.gettype()
True
0
You cannot call a method on a null-valued expression.
At line:1 char:38
+ $l_t2 = emptyArray; $l_t2 -eq $null; $l_t2.Count; $l_t2.gettype()
+                                      ~~~~~~~~~~~~~~~
  + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
  + FullyQualifiedErrorId : InvokeMethodOnNull
    >$l_t += $l_t2; $l_t.Count
0
    >$l_t3 = $null; $l_t3 -eq $null;$l_t3.gettype()
True
You cannot call a method on a null-valued expression.
At line:1 char:32
+ $l_t3 = $null; $l_t3 -eq $null;$l_t3.gettype()
+                                ~~~~~~~~~~~~~~~
  + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
  + FullyQualifiedErrorId : InvokeMethodOnNull
    >$l_t += $l_t3; $l_t.count
1
    >function addToArray($l_a, $l_b) { $l_a += $l_b; $l_a.count }
    >$l_t = @(); $l_t.Count
0
    >addToArray $l_t $l_t1
0
    >addToArray $l_t $l_t2
1
Run Code Online (Sandbox Code Playgroud)

那么如何以及为何$l_t2与众不同$l_t3?特别是,$l_t2真的$null与否?请注意,这$l_t2不是一个空数组($l_t1是,并且$l_t1 -eq $null没有返回任何内容,如预期的那样),但它也不是真的$null,就像$l_t3.特别是,$l_t2.count将返回0而不是错误,此外,加入$l_t2$l_t行为类似于添加一个空的阵列,不象添加$null.当它作为参数传递给函数时,为什么$l_t2突然变得"更多$null" addToArray????????

任何人都可以解释这种行为,或者指出可以解释它的文档吗?

编辑:以下PetSerAl的答案是正确的. 我也在同一个问题上找到了这个stackOverflow帖子.

Powershell版本信息:

    >$PSVersionTable
Name                           Value                                                                                        
----                           -----                                                                                        
WSManStackVersion              3.0                                                                                          
PSCompatibleVersions           {1.0, 2.0, 3.0}                                                                              
SerializationVersion           1.1.0.1                                                                                      
BuildVersion                   6.2.9200.16481                                                                               
PSVersion                      3.0                                                                                          
CLRVersion                     4.0.30319.1026                                                                               
PSRemotingProtocolVersion      2.2                                                                                          
Run Code Online (Sandbox Code Playgroud)

use*_*407 16

特别是,$l_t2真的$null与否?

$l_t2不是$null,但是[System.Management.Automation.Internal.AutomationNull]::Value.这是一个特例PSObject.管道返回零对象时返回.这是你如何检查它:

$a=&{} #shortest, I know, pipeline, that returns zero objects
$b=[System.Management.Automation.Internal.AutomationNull]::Value

$ReferenceEquals=[Object].GetMethod('ReferenceEquals')

$ReferenceEquals.Invoke($null,($a,$null)) #returns False
$ReferenceEquals.Invoke($null,($a,$b))    #returns True
Run Code Online (Sandbox Code Playgroud)

ReferenceEquals通过反射来调用,以防止AutomationNullPowerShell 将转换为$ null.

$l_t1 -eq $null 没有回报

对我来说,它返回一个空数组,正如我所期望的那样.

$l_t2.count 返回0

这是PowerShell v3新功能:

您现在可以在任何对象上使用Count或Length,即使它没有该属性.如果对象没有Count或Length属性,它将返回1(或$ null为$ null).具有Count或Length属性的对象将继续像往常一样工作.

PS> $a = 42 
PS> $a.Count 
1
Run Code Online (Sandbox Code Playgroud)

 

当它作为参数传递给函数时,为什么$l_t2突然变得"更多$null" addToArray????????

在某些情况下,PowerShell似乎转换AutomationNull$null调用.NET方法.在PowerShell v2中,即使保存AutomationNull到变量,它也会转换为$null.


mkl*_*nt0 8

为了补充 PetSerAl的出色答案,我们提供了一个实用的摘要

  • 碰巧不产生任何输出的命令不会返回$null,而是[System.Management.Automation.Internal.AutomationNull]::Value单例,可以将其视为“数组值$null”,或者换句话说,为null数组

    • 请注意,由于PowerShell展开了集合的包装,因此即使显式输出空集合对象的命令(例如,@()没有输出)(除非明确禁止枚举,例如使用Write-Output -NoEnumerate)。
  • 简而言之,这个特殊值的行为就像$null标量上下文中一样,在数组 / 管道上下文中就像一个空数组,如下面的示例所示。

注意事项

  • [System.Management.Automation.Internal.AutomationNull]::Value作为cmdlet /函数参数值传递时始终会将其转换为$null

  • PSv3 +中,即使不是实际(标量)$null不会foreach循环中枚举;但是,它在管道中枚举-参见底部。

  • PSv2-中将空数组保存在变量中会悄悄地将其转换为,$null$nullforeach循环中(而不只是在管道中)进行枚举 -参见底部。

# A true $null value:
$v1 = $null  

# An operation with no output returns
# the [System.Management.Automation.Internal.AutomationNull]::Value singleton,
# which is treated like $null in a scalar expression context, 
# but behaves like an empty array in a pipeline or array expression context.
$v2 = & {}  # calling (&) an empty script block ({}) produces no output

# In a *scalar expression*, [System.Management.Automation.Internal.AutomationNull]::Value 
# is implicitly converted to $null, which is why all of the following commands
# return $true.
$null -eq $v2
$v1 -eq $v2
$null -eq [System.Management.Automation.Internal.AutomationNull]::Value
& { param($param) $null -eq $param } $v2

# By contrast, in a *pipeline*, $null and
# [System.Management.Automation.Internal.AutomationNull]::Value
# are NOT the same:

# Actual $null *is* sent as data through the pipeline:
# The (implied) -Process block executes once.
$v1 | % { 'input received' } # -> 'input received'

# [System.Management.Automation.Internal.AutomationNull]::Value is *not* sent 
# as data through the pipeline, it behaves like an empty array:
# The (implied) -Process block does *not* execute (but -Begin and -End blocks would).
$v2 | % { 'input received' } # -> NO output; effectively like: @() | % { 'input received' }

# Similarly, in an *array expression* context
# [System.Management.Automation.Internal.AutomationNull]::Value also behaves
# like an empty array:
(@() + $v2).Count # -> 0 - contrast with (@() + $v1).Count, which returns 1.

# CAVEAT: Passing [System.Management.Automation.Internal.AutomationNull]::Value to 
# *any parameter* converts it to actual $null, whether that parameter is an
# array parameter or not.
# Passing [System.Management.Automation.Internal.AutomationNull]::Value is equivalent
# to passing true $null or omitting the parameter (by contrast,
# passing @() would result in an actual, empty array instance).
& { param([object[]] $param) 
    [Object].GetMethod('ReferenceEquals').Invoke($null, @($null, $param)) 
  } $v2  # -> $true; would be the same with $v1 or no argument at all.
Run Code Online (Sandbox Code Playgroud)

[System.Management.Automation.Internal.AutomationNull]::Value文档指出:

任何不返回实际值的操作都应返回AutomationNull.Value

任何评估Windows PowerShell表达式的组件都应准备好处理接收和丢弃此结果的过程。在需要值的评估中收到时,应将其替换为null


PSv2与PSv3 +和一般不一致之处

PSv2在存储在变量中的值之间[System.Management.Automation.Internal.AutomationNull]::Value和之间没有区别:$null

  • 使用无输出指令,直接foreach声明中/管道没有如预期的工作 -什么是通过管道发送/在foreach没有输入回路:

    Get-ChildItem nosuchfiles* | ForEach-Object { 'hi' }
    foreach ($f in (Get-ChildItem nosuchfiles*)) { 'hi' }
    
    Run Code Online (Sandbox Code Playgroud)
  • 相反,如果将无输出命令保存在变量中或使用了显式命令$null,则行为是不同的

    # Store the output from a no-output command in a variable.
    $result = Get-ChildItem nosuchfiles* # PSv2-: quiet conversion to $null happens here
    
    # Enumerate the variable.
    $result | ForEach-Object { 'hi1' }
    foreach ($f in $result) { 'hi2' }
    
    # Enumerate a $null literal.
    $null | ForEach-Object { 'hi3' }
    foreach ($f in $null) { 'hi4' }
    
    Run Code Online (Sandbox Code Playgroud)
    • PSV2所有上述命令输出一个字符串开始的hi,因为$null 通过管道发送/被列举foreach
      在PSv3 +不同,[System.Management.Automation.Internal.AutomationNull]::Value转换为$null分配给一个变量,并且$null始终在PSV2列举

    • PSv3 +PSv3中行为发生了变化,无论好坏:

      • 没有通过管道为枚举命令发送$result:将foreach被循环进入,因为[System.Management.Automation.Internal.AutomationNull]::Value保存分配给一个变量时,与PSV2。

      • 可能更糟: foreach不再枚举$null(无论是指定为文字还是存储在变量中),因此foreach ($f in $null) { 'hi4' }可能令人惊讶地未产生任何输出。
        从好的方面来说,新行为不再枚举未初始化的变量,该变量的计算结果为$null(除非与一起被阻止Set-StrictMode)。
        但是,一般而言,$null鉴于PSv2无法将空集合值存储在变量中,因此不枚举在PSv2中更为合理。

总结中,PSv3 +行为

  • 带走区分的能力$null,并[System.Management.Automation.Internal.AutomationNull]::Value在上下文foreach声明

  • 从而引入了与管道行为的不一致之处,在这种情况下尊重这种区别。

为了向后兼容,无法更改当前行为。对此GitHub的评论提出了一种解决潜在不一致的潜在PowerShell版本的方法,这些版本不需要向后兼容。

  • 最后,对这种看似怪异的行为进行了简洁明了的解释。对我来说,这更有意义。谢谢! (2认同)