显然,在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
答案 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数组 。
@()
的命令)也会输出 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会按如下方式确定返回值的数据类型:
$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对象。