为什么和如何这两个$空值不同?为什么和如何这两个$空值不同?(Why and how are the

2019-05-12 07:23发布

(版本3)显然,在PowerShell中不是所有的$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的答案是正确的。 我也发现了同样的问题,这个计算器职位。

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                                                                                          

Answer 1:

尤其是$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通反射,从而防止转换从AutomationNull为$ null通过PowerShell的。

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

对我来说,它返回一个空数组,当我从它的期望。

$l_t2.count返回0

这是一个PowerShell的V3的新功能 :

现在,您可以使用数量或长度的任何对象,即使它没有足够的财产。 如果对象没有一个计数或长度属性,则返回1(或0 $空)。 具有计数或长度属性的对象将继续工作,因为他们总是有。

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

 

又为何$l_t2似乎突然变得“更$null当它在这个函数会得到” addToArray作为参数???????

看来,PowerShell的转换AutomationNull$null在某些情况下,比如调用.NET方法。 在PowerShell中V2,节约即使AutomationNull给一个变量它被转换为$null



Answer 2:

为了补充 PetSerAl最伟大的答案用务实的总结

  • 这事发生在没有产生输出命令不返回$null ,但[System.Management.Automation.Internal.AutomationNull]::Value ,这可以被看作是一个“阵列值$null ”,或到硬币术语, 空数组

    • 需要注意的是,由于集合PowerShell的解缠,即使明确地输出空集合对象诸如命令@() 没有输出(除非枚举明确地防止,例如用Write-Output -NoEnumerate )。
  • 总之,这个特殊的值的行为就像$null标量上下文,并像阵列 / 管道上下文空数组 ,如下面的例子演示。

注意事项

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

    • 见这个GitHub的问题 。
  • PSv3 +,甚至是实际的(标量) $null不是在列举foreach循环 ; 列举了管道,但是-见底部。

  • PSv2-, 节省了空数组变量中悄然转换它为$null$null在一个枚举foreach循环以及 (不只是在流水线) -见底部。

# 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.

[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' } 
  • 相反, 如果无输出指令被保存在一个变量或显式$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: 所有上述命令的输出开始用字符串的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更有道理,因为它不能存储在一个变量的空收藏价值。

综上所述 ,PSv3 +行为

  • 带走区分的能力$null[System.Management.Automation.Internal.AutomationNull]::Value的背景下foreach语句

  • 从而引入了流水线的操作,在这种区别尊重不一致。

为了向后兼容的缘故,当前行为不能被改变。 GitHub上的这个评论提出了一种方法来解决这些矛盾的需要不能向后兼容未来潜在的PowerShell版本。



Answer 3:

当您从一个PowerShell函数返回一个集合,在默认情况下PowerShell的决定返回值的数据类型,如下所示:

  • 如果集合有一个以上的元素,返回的结果是一个数组。 需要注意的是返回结果的数据类型的System.Array即使正在返回的对象是不同类型的集合。
  • 如果集合具有单个元件,所述返回的结果是,元素的值,而不是一个元件的集合,并返回结果的数据类型是该元素的数据类型。
  • 如果集合为空,返回结果为$空

$l_t = @()分配一个空数组到$ l_t。

$l_t2 = emptyArray $空赋给$ l_t2,因为函数emptyArray返回一个空集,因此,返回结果为$空

$ l_t2$ l_t3 都是空的,他们的行为方式相同。 既然你已经预先声明的$ l_t为空数组,当你添加或者$ l_t2$ l_t3到它,无论是用+ =运营商或addToArray功能,一个元素被** $空*添加到其值阵列。

如果要强制函数,以便保持你返回集合对象的数据类型,用逗号:

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`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对象的功能,当我第一次遇到这个问题。 返回结果是哈希表,而不是数据表的阵列。 更改return $my_datatable_objectreturn ,$my_datatable_object取得函数返回一个实际DataTable对象。



文章来源: Why and how are these two $null values different?