这两个 $null 值为什么不同以及如何不同?
Why and how are these two $null values different?
显然,在 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
returns 没有,正如预期的那样),但它也不是真正的 $null
,如 $l_t3
。特别是 $l_t2.count
returns 0 而不是错误,此外,将 $l_t2
添加到 $l_t
的行为就像添加一个空数组,而不是添加 $null
。为什么 $l_t2
在作为参数传入函数 addToArray
时突然变成了 "more $null
" ???????
任何人都可以解释这种行为,或者给我指出可以解释它的文档吗?
编辑:
下面 PetSerAl 的回答是正确的。 I have also found this Whosebug 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
In particular, is $l_t2
really $null
or not?
$l_t2
不是 $null
,而是 [System.Management.Automation.Internal.AutomationNull]::Value
。它是 PSObject
的一个特殊实例。当管道 returns 零个对象时返回。这就是您可以检查它的方式:
$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
returns nothing
对我来说,它 returns 一个空数组,正如我所期望的那样。
$l_t2.count
returns 0
这是一个new feature of PowerShell v3:
You can now use Count or Length on any object, even if it didn’t have the property. If the object didn’t have a Count or Length property, it will will return 1 (or 0 for $null). Objects that have Count or Length properties will continue to work as they always have.
PS> $a = 42
PS> $a.Count
1
And why does $l_t2
suddenly seem to become "more $null
" when it gets passed in the the function addToArray
as a parameter???????
在某些情况下,PowerShell 似乎会将 AutomationNull
转换为 $null
,例如调用 .NET 方法。在 PowerShell v2 中,即使将 AutomationNull
保存到变量,它也会转换为 $null
.
当您从 PowerShell 函数 return 集合时,默认情况下 PowerShell 确定 return 值的数据类型如下:
- 如果集合有多个元素,return结果是一个数组。请注意,return 结果的数据类型是 System.Array,即使被 returned 的对象是不同类型的集合。
- 如果集合只有一个元素,return结果是该元素的值,而不是一个元素的集合,return结果的数据类型是数据该元素的类型。
- 如果集合为空,return结果为$null
$l_t = @()
将空数组分配给 $l_t。
$l_t2 = emptyArray
将 $null 赋值给 $l_t2,因为函数 emptyArray return 是一个空集合,因此 return 结果是 $null.
$l_t2和$l_t3是 均为 null,并且它们的行为方式相同。由于您已将 $l_t 预先声明为空数组,因此当您添加 $l_t2 或$l_t3 到它,使用 += 运算符或 addToArray 函数, 一个值为 **$null* 的元素被添加到数组中。
如果您想强制函数保留您正在 returning 的集合对象的数据类型,请使用逗号运算符:
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后面的空括号是多余的。如果您使用它们来声明参数,则只需要在函数名称后加上括号。
需要注意的一个有趣的一点是,逗号运算符 并不一定 必须使 return 值成为数组。
回想一下,正如我在第一个要点中提到的,默认情况下,具有多个元素的集合的 return 结果的数据类型是 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。
但是,如果您 return 它来自一个函数,在函数中 $list 的数据类型是 List`1,但它被 return 编辑为包含相同元素的 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
如果您希望 return 结果是与您正在 returning 的函数中的数据类型相同的数据类型的集合,逗号运算符将实现这一点:
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 更改您正在 returning 的对象的数据类型,并且您希望 return 值保留对象的原始数据类型,您都可以这样做通过在被 returned 的对象前面加上逗号。我第一次遇到这个问题是在编写查询数据库并 returned DataTable 对象的函数时。 return 结果是哈希表数组而不是数据表。将 return $my_datatable_object
更改为 return ,$my_datatable_object
使函数 return 成为实际的 DataTable 对象。
用实用摘要补充:
恰好产生无输出的命令不会return$null
,而是[System.Management.Automation.Internal.AutomationNull]::Value
单例,
可以将其视为“数组值 $null
”,或者,创造一个术语,空枚举。它有时也被称为“AutomationNull”,因为它的类型名称。
- 请注意,由于 PowerShell 对集合的展开,即使是显式输出空集合对象的命令(例如
@()
)也有 no 输出(除非显式枚举阻止,例如 Write-Output -NoEnumerate
).
简而言之,这个特殊值 在 标量 上下文中表现得像 $null
,并且像 枚举上下文中的空数组,特别是管道中的空数组,如下例所示。
注意事项:
将[System.Management.Automation.Internal.AutomationNull]::Value
作为cmdlet/函数传递参数值总是转换它到 $null
.
在PSv3+中,即使是实际(标量)$null
也是不是在foreach
循环中枚举;它 是 在管道中枚举,但是 - 请参阅底部。
在PSv2-中,在变量中保存一个空枚举悄悄地把它转换成$null
和 $null
也在 foreach
循环中被枚举 (不仅仅是在管道中) - 见底部。
# A true $null value:
$trueNull = $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.
$automationNull = & {} # 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 $automationNull
$trueNull -eq $automationNull
$null -eq [System.Management.Automation.Internal.AutomationNull]::Value
& { param($param) $null -eq $param } $automationNull
# 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.
$trueNull | % { '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).
$automationNull | % { '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:
(@() + $automationNull).Count # -> 0 - contrast with (@() + $trueNull).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))
} $automationNull # -> $true; would be the same with $trueNull or no argument at all.
[System.Management.Automation.Internal.AutomationNull]::Value
文档指出:
Any operation that returns no actual value should return AutomationNull.Value
.
Any component that evaluates a Windows PowerShell expression should be prepared to deal with receiving and discarding this result. When received in an evaluation where a value is required, it should be replaced with null
.
PSv2 与 PSv3+,以及普遍的不一致:
PSv2 对于存储在 variables:
中的值没有区分 [System.Management.Automation.Internal.AutomationNull]::Value
和 $null
在foreach
语句/管道中直接使用无输出命令did 按预期工作 - 没有通过管道发送任何内容/未输入 foreach
循环:
Get-ChildItem nosuchfiles* | ForEach-Object { 'hi' }
foreach ($f in (Get-ChildItem nosuchfiles*)) { 'hi' }
相比之下,如果将无输出命令保存在变量中或使用显式$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' }
PSv2: 所有以上命令输出一个以[=34开头的字符串=],因为$null
是通过管道发送/被foreach
枚举:
与 PSv3+ 不同,[System.Management.Automation.Internal.AutomationNull]::Value
在 分配给变量 [=184 时 转换为 $null
=],而 $null
在 PSv2.
中总是
PSv3+:行为在 PSv3 中发生了变化,无论是好是坏:
更好:没有通过管道发送枚举$result
:foreach
循环未进入,因为[System.Management.Automation.Internal.AutomationNull]::Value
保留 分配给变量时 ,与 PSv2 不同。
可能更糟: foreach
不再枚举 $null
(无论指定为文字或存储在变量中),因此 foreach ($f in $null) { 'hi4' }
可能会令人惊讶地产生 no 输出。
从好的方面来说,新行为不再枚举 未初始化的变量 ,其计算结果为 $null
(除非与 Set-StrictMode
一起阻止)。
但是,一般来说,不枚举 $null
在 PSv2 中更合理,因为它无法将空集合值存储在变量中。
在摘要中,PSv3+行为:
取消了在 foreach
语句
的上下文中区分 $null
和 [System.Management.Automation.Internal.AutomationNull]::Value
的能力
因此引入了与管道行为的不一致,其中这种区别是。
为了向后兼容,无法更改当前行为。 This comment on GitHub 针对不需要向后兼容的(假设的)潜在的未来 PowerShell 版本提出了解决这些不一致的方法。
显然,在 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
returns 没有,正如预期的那样),但它也不是真正的 $null
,如 $l_t3
。特别是 $l_t2.count
returns 0 而不是错误,此外,将 $l_t2
添加到 $l_t
的行为就像添加一个空数组,而不是添加 $null
。为什么 $l_t2
在作为参数传入函数 addToArray
时突然变成了 "more $null
" ???????
任何人都可以解释这种行为,或者给我指出可以解释它的文档吗?
编辑: 下面 PetSerAl 的回答是正确的。 I have also found this Whosebug 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
In particular, is
$l_t2
really$null
or not?
$l_t2
不是 $null
,而是 [System.Management.Automation.Internal.AutomationNull]::Value
。它是 PSObject
的一个特殊实例。当管道 returns 零个对象时返回。这就是您可以检查它的方式:
$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
returns nothing
对我来说,它 returns 一个空数组,正如我所期望的那样。
$l_t2.count
returns 0
这是一个new feature of PowerShell v3:
You can now use Count or Length on any object, even if it didn’t have the property. If the object didn’t have a Count or Length property, it will will return 1 (or 0 for $null). Objects that have Count or Length properties will continue to work as they always have.
PS> $a = 42 PS> $a.Count 1
And why does
$l_t2
suddenly seem to become "more$null
" when it gets passed in the the functionaddToArray
as a parameter???????
在某些情况下,PowerShell 似乎会将 AutomationNull
转换为 $null
,例如调用 .NET 方法。在 PowerShell v2 中,即使将 AutomationNull
保存到变量,它也会转换为 $null
.
当您从 PowerShell 函数 return 集合时,默认情况下 PowerShell 确定 return 值的数据类型如下:
- 如果集合有多个元素,return结果是一个数组。请注意,return 结果的数据类型是 System.Array,即使被 returned 的对象是不同类型的集合。
- 如果集合只有一个元素,return结果是该元素的值,而不是一个元素的集合,return结果的数据类型是数据该元素的类型。
- 如果集合为空,return结果为$null
$l_t = @()
将空数组分配给 $l_t。
$l_t2 = emptyArray
将 $null 赋值给 $l_t2,因为函数 emptyArray return 是一个空集合,因此 return 结果是 $null.
$l_t2和$l_t3是 均为 null,并且它们的行为方式相同。由于您已将 $l_t 预先声明为空数组,因此当您添加 $l_t2 或$l_t3 到它,使用 += 运算符或 addToArray 函数, 一个值为 **$null* 的元素被添加到数组中。
如果您想强制函数保留您正在 returning 的集合对象的数据类型,请使用逗号运算符:
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后面的空括号是多余的。如果您使用它们来声明参数,则只需要在函数名称后加上括号。
需要注意的一个有趣的一点是,逗号运算符 并不一定 必须使 return 值成为数组。
回想一下,正如我在第一个要点中提到的,默认情况下,具有多个元素的集合的 return 结果的数据类型是 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。
但是,如果您 return 它来自一个函数,在函数中 $list 的数据类型是 List`1,但它被 return 编辑为包含相同元素的 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
如果您希望 return 结果是与您正在 returning 的函数中的数据类型相同的数据类型的集合,逗号运算符将实现这一点:
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 更改您正在 returning 的对象的数据类型,并且您希望 return 值保留对象的原始数据类型,您都可以这样做通过在被 returned 的对象前面加上逗号。我第一次遇到这个问题是在编写查询数据库并 returned DataTable 对象的函数时。 return 结果是哈希表数组而不是数据表。将 return $my_datatable_object
更改为 return ,$my_datatable_object
使函数 return 成为实际的 DataTable 对象。
用实用摘要补充
恰好产生无输出的命令不会return
$null
,而是[System.Management.Automation.Internal.AutomationNull]::Value
单例, 可以将其视为“数组值$null
”,或者,创造一个术语,空枚举。它有时也被称为“AutomationNull”,因为它的类型名称。- 请注意,由于 PowerShell 对集合的展开,即使是显式输出空集合对象的命令(例如
@()
)也有 no 输出(除非显式枚举阻止,例如Write-Output -NoEnumerate
).
- 请注意,由于 PowerShell 对集合的展开,即使是显式输出空集合对象的命令(例如
简而言之,这个特殊值 在 标量 上下文中表现得像
$null
,并且像 枚举上下文中的空数组,特别是管道中的空数组,如下例所示。
注意事项:
将
[System.Management.Automation.Internal.AutomationNull]::Value
作为cmdlet/函数传递参数值总是转换它到$null
.在PSv3+中,即使是实际(标量)
$null
也是不是在foreach
循环中枚举;它 是 在管道中枚举,但是 - 请参阅底部。在PSv2-中,在变量中保存一个空枚举悄悄地把它转换成
$null
和$null
也在foreach
循环中被枚举 (不仅仅是在管道中) - 见底部。
# A true $null value:
$trueNull = $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.
$automationNull = & {} # 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 $automationNull
$trueNull -eq $automationNull
$null -eq [System.Management.Automation.Internal.AutomationNull]::Value
& { param($param) $null -eq $param } $automationNull
# 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.
$trueNull | % { '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).
$automationNull | % { '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:
(@() + $automationNull).Count # -> 0 - contrast with (@() + $trueNull).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))
} $automationNull # -> $true; would be the same with $trueNull or no argument at all.
[System.Management.Automation.Internal.AutomationNull]::Value
文档指出:
Any operation that returns no actual value should return
AutomationNull.Value
.
Any component that evaluates a Windows PowerShell expression should be prepared to deal with receiving and discarding this result. When received in an evaluation where a value is required, it should be replaced with
null
.
PSv2 与 PSv3+,以及普遍的不一致:
PSv2 对于存储在 variables:
中的值没有区分[System.Management.Automation.Internal.AutomationNull]::Value
和 $null
在
foreach
语句/管道中直接使用无输出命令did 按预期工作 - 没有通过管道发送任何内容/未输入foreach
循环:Get-ChildItem nosuchfiles* | ForEach-Object { 'hi' } foreach ($f in (Get-ChildItem nosuchfiles*)) { 'hi' }
相比之下,如果将无输出命令保存在变量中或使用显式
$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' }
PSv2: 所有以上命令输出一个以[=34开头的字符串=],因为
中总是$null
是通过管道发送/被foreach
枚举:
与 PSv3+ 不同,[System.Management.Automation.Internal.AutomationNull]::Value
在 分配给变量 [=184 时 转换为$null
=],而$null
在 PSv2.PSv3+:行为在 PSv3 中发生了变化,无论是好是坏:
更好:没有通过管道发送枚举
$result
:foreach
循环未进入,因为[System.Management.Automation.Internal.AutomationNull]::Value
保留 分配给变量时 ,与 PSv2 不同。可能更糟:
foreach
不再枚举$null
(无论指定为文字或存储在变量中),因此foreach ($f in $null) { 'hi4' }
可能会令人惊讶地产生 no 输出。
从好的方面来说,新行为不再枚举 未初始化的变量 ,其计算结果为$null
(除非与Set-StrictMode
一起阻止)。
但是,一般来说,不枚举$null
在 PSv2 中更合理,因为它无法将空集合值存储在变量中。
在摘要中,PSv3+行为:
取消了在
的上下文中区分foreach
语句$null
和[System.Management.Automation.Internal.AutomationNull]::Value
的能力因此引入了与管道行为的不一致,其中这种区别是。
为了向后兼容,无法更改当前行为。 This comment on GitHub 针对不需要向后兼容的(假设的)潜在的未来 PowerShell 版本提出了解决这些不一致的方法。