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

时间:2015-05-03 17:21:05

标签: 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

那么$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的答案是正确的。 I have also found this stackOverflow post on the same issue.

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                                                                                          

3 个答案:

答案 0 :(得分: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

我通过反射调用ReferenceEquals来阻止PowerShell从AutomationNull转换为$ null。

  

$l_t1 -eq $null不返回任何内容

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

  

$l_t2.count返回0

这是new feature of PowerShell v3

  

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

PS> $a = 42 
PS> $a.Count 
1

  

为什么$l_t2在函数$null作为参数传递时突然变得“更addToArray”???????

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

答案 1 :(得分:8)

使用实用摘要 补充 PetSerAl's great answer

  • 恰好产生无输出的命令不会返回$null,而是返回[System.Management.Automation.Internal.AutomationNull]::Value单例, 这可以被认为是一个"数组值$null"或者,为了一个术语, null数组

    • 请注意,由于PowerShell对集合的解包,即使是显式输出空集合对象(如@()的命令)也会输出 no (除非明确阻止枚举) ,例如Write-Output -NoEnumerate)。
  • 简而言之,此特殊值的行为类似于标量上下文中的$null,就像数组中的空数组一样 / 管道上下文,如下面的示例所示。

警告

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

  • PSv3 + 中,即使实际(标量) $null在<{1}}循环中枚举的 强>;然而, 在管道中进行了枚举 - 请参阅底部。

  • PSv2 - 中,变量中保存空数组,将其静静地转换为foreach $null也在$null循环中枚举(不仅仅是在管道中) - 请参阅底部。

foreach

[System.Management.Automation.Internal.AutomationNull]::Value documentation州:

  

任何不返回实际值的操作都应返回# 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.

     

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

PSv2与PSv3 +和一般不一致

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

  • $null语句/管道中使用无输出命令 按预期工作 - 没有通过管道发送/ foreach循环未输入:

    foreach
  • 相比之下,如果在变量中保存了无输出命令或使用了显式Get-ChildItem nosuchfiles* | ForEach-Object { 'hi' } foreach ($f in (Get-ChildItem nosuchfiles*)) { 'hi' } ,则行为不同

    $null
    • PSv2 以上命令的所有输出以# 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' } 开头的字符串,因为{{1} } 通过管道发送/由hi枚举:
      与PSv3 +不同, $null 分配给变量 时转换为foreach {{ 1}} 始终枚举PSv2

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

      • 更好 通过管道为枚举[System.Management.Automation.Internal.AutomationNull]::Value的命令发送无$null循环为已输入,因为 $null在分配给变量保留,与PSv2不同。

      • 可能更糟糕: $result不再枚举foreach (无论是指定为文字还是存储在变量中),所以[System.Management.Automation.Internal.AutomationNull]::Value或许会令人惊讶地产生 no 输出 从好的方面来说,新行为不再枚举未初始化的变量,其评估为foreach(除非与$null完全一致)。
        但是,一般情况下,由于无法将空集合值存储在变量中,因此在PSv2中枚举foreach ($f in $null) { 'hi4' }不合理。

摘要中, PSv3 +行为

  • $null语句的上下文中删除了区分Set-StrictMode$null的能力

  • 因此引入了与管道行为的不一致,其中

为了向后兼容,不能更改当前行为。 This comment on GitHub提出了一种方法来解决这些不一致的未来PowerShell版本,这些版本不需要向后兼容。

答案 2 :(得分:3)

从PowerShell函数返回集合时,默认情况下PowerShell会按如下方式确定返回值的数据类型:

  • 如果集合有多个元素,则返回结果为数组。请注意,即使返回的对象是不同类型的集合,返回结果的数据类型也是 System.Array
  • 如果集合具有单个元素,则返回结果是该元素的值,而不是一个元素的集合,并且返回结果的数据类型是该元素的数据类型。
  • 如果集合为空,则返回结果为 $ null

$l_t = @() $ l_t 指定一个空数组。

$l_t2 = emptyArray $ null 分配给 $ l_t2 ,因为函数 emptyArray 会返回一个空集合,因此返回结果是 $ null

$ l_t2 $ l_t3 均为空,且行为方式相同。由于您已将 $ l_t 预先声明为空数组,因此当您向其添加 $ l_t2 $ l_t3 时,使用 + = 运算符或 addToArray 函数,将值为** $ null *的元素添加到数组中。

如果要强制该函数保留您要返回的集合对象的数据类型,请使用逗号运算符:

PS> function emptyArray {,@()}
PS> $l_t2 = emptyArray
PS> $l_t2.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

PS> $l_t2.Count
0

注意:函数声明中 emtpyArray 之后的空括号是多余的。如果您正在使用它们来声明参数,则只需在函数名后面加括号。

<小时/> 需要注意的一点是,逗号运算符不会必然会使返回值成为数组。

回想一下,正如我在第一个项目符号点中提到的,默认情况下,具有多个元素的集合的返回结果的数据类型是 System.Array ,而不管实际的数据类型是什么采集。例如:

PS> $list = New-Object -TypeName System.Collections.Generic.List[int]
PS> $list.Add(1)
PS> $list.Add(2)
PS> $list.Count
2
PS> $list.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     List`1                                   System.Object

请注意,此集合的数据类型为 List`1 ,而不是 System.Array

但是,如果您从某个函数返回它,则在该函数中 $ list 的数据类型为 List`1 ,但它作为一个返回 System.Array 包含相同的元素。

PS> function Get-List {$list = New-Object -TypeName System.Collections.Generic.List[int]; $list.Add(1); $list.Add(2); return $list}
PS> $l = Get-List
PS> $l.Count
2
PS> $l.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

如果您希望返回结果是与您返回的函数中的数据类型相同的数据类型的集合,则逗号运算符将完成该操作:

PS> function Get-List {$list = New-Object -TypeName System.Collections.Generic.List[int]; $list.Add(1); $list.Add(2); return ,$list}
PS> $l = Get-List
PS> $l.Count
2
PS> $l.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     List`1                                   System.Object

这并不仅限于类似数组的集合对象。就我所见,任何时候PowerShell更改您返回的对象的数据类型,并且您希望返回值保留对象的原始数据类型,您可以通过在用逗号返回的对象之前。我在编写查询数据库并返回DataTable对象的函数时首次遇到此问题。返回结果是一个哈希表数组而不是DataTable。将return $my_datatable_object更改为return ,$my_datatable_object会使函数返回实际的DataTable对象。