为什么对伪数组属性进行逻辑测试会返回true?

时间:2018-07-09 22:42:43

标签: powershell

我最近在有一个对象数组的地方犯了一个错误,我不小心对数组的属性进行了布尔值评估,但是我 meant 对数组中的单个对象进行了评估。我仅在测试过程中发现错误,因为我期望$TRUE时得到一个$FALSE值。我创建了一个小脚本来说明这种情况。我不明白的是为什么这个不存在的数组属性总是求值为true?

# Set-StrictMode -Version Latest; # Catch dumb programmer mistakes.
$PSVersionTable.PSVersion
Write-Output "`n"

$coArray = @()
foreach ($id in 0,1)
  {
    $cObj = New-Object PSobject
    $cObj | Add-Member -type NoteProperty -name ID   -value $id
    $cObj | Add-Member -type NoteProperty -name Bool -value $false
    $coArray += $cObj
  }

foreach ($cObj in $coArray)
  {
    # What I meant to do...

    if ($cObj.Bool)     { Write-Output 'ON OBJECT: Bool is true'  }
    else                { Write-Output 'ON OBJECT: Bool is false' }

    # What I actually did...

    if ($coArray.Bool)  { Write-Output 'ON ARRAY: Bool is true'   }
    else                { Write-Output 'ON ARRAY: Bool is false'  }

    # What if there is no such object property?

    if ($coArray.Bogus) { Write-Output 'ON ARRAY: Bogus is true'  }
    else                { Write-Output 'ON ARRAY: Bogus is false' }
  }

以下是输出:

Major  Minor  Build  Revision
-----  -----  -----  --------
5      1      14393  2339


ON OBJECT: Bool is false
ON ARRAY: Bool is true
ON ARRAY: Bogus is true
ON OBJECT: Bool is false
ON ARRAY: Bool is true
ON ARRAY: Bogus is true

起初,我认为对于放错了位置的现有对象属性,它返回true,因为它确认了数组中的至少一个对象具有该属性。但是,如果您关闭strict并引用了一个完全伪造的属性(如上所示),它仍然会返回true。

2 个答案:

答案 0 :(得分:2)

嗯...所以这就是我的想法。我认为这是Powershell现在在数组对象上展开属性的方式的影响。 Powershell绝对是真实的。

第一个测试if ($cObj.Bool)确实按照人们期望的方式工作。第二个测试if ($coArray.Bool)使powershell创建一个新数组,该数组仅包含每个对象中该属性的内容。可以通过以下方式显示:

C:\Users\Rob> $coArray.bool
False
False

因此在真实的Powershell世界中,这确实存在,因此是真的。

第三项测试if ($coArray.Bogus)几乎具有相同的作用。可以通过以下方式显示:

Get-Member -InputObject $coArray.bogus
  TypeName: System.Object[]

Name           MemberType            Definition
----           ----------            ----------
Count          AliasProperty         Count = Length
Add            Method                int IList.Add(System.Object value)
Address        Method                System.Object&, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a...
Clear          Method                void IList.Clear()
Clone          Method                System.Object Clone(), System.Object ICloneable.Clone()
CompareTo      Method                int IStructuralComparable.CompareTo(System.Object other, System.Collections.ICo...
Contains       Method                bool IList.Contains(System.Object value)
CopyTo         Method                void CopyTo(array array, int index), void CopyTo(array array, long index), void...
Equals         Method                bool Equals(System.Object obj), bool IStructuralEquatable.Equals(System.Object ...
Get            Method                System.Object Get(int )
GetEnumerator  Method                System.Collections.IEnumerator GetEnumerator(), System.Collections.IEnumerator ...
GetHashCode    Method                int GetHashCode(), int IStructuralEquatable.GetHashCode(System.Collections.IEqu...
GetLength      Method                int GetLength(int dimension)
GetLongLength  Method                long GetLongLength(int dimension)
GetLowerBound  Method                int GetLowerBound(int dimension)
GetType        Method                type GetType()
GetUpperBound  Method                int GetUpperBound(int dimension)
GetValue       Method                System.Object GetValue(Params int[] indices), System.Object GetValue(int index)...
IndexOf        Method                int IList.IndexOf(System.Object value)
Initialize     Method                void Initialize()
Insert         Method                void IList.Insert(int index, System.Object value)
Remove         Method                void IList.Remove(System.Object value)
RemoveAt       Method                void IList.RemoveAt(int index)
Set            Method                void Set(int , System.Object )
SetValue       Method                void SetValue(System.Object value, int index), void SetValue(System.Object valu...
ToString       Method                string ToString()
Item           ParameterizedProperty System.Object IList.Item(int index) {get;set;}
IsFixedSize    Property              bool IsFixedSize {get;}
IsReadOnly     Property              bool IsReadOnly {get;}
IsSynchronized Property              bool IsSynchronized {get;}
Length         Property              int Length {get;}
LongLength     Property              long LongLength {get;}
Rank           Property              int Rank {get;}
SyncRoot       Property              System.Object SyncRoot {get;}

因此,正如所指出的,仅在数组中执行一个对象会导致所有测试为假。这是因为数组中只有一项意味着它只有一个属性可以展开。 Powershell将其展开为单例对象而不是值数组。因此,如果没有名称相同的属性,则单例为null。看到这里:

C:\Users\Rob> $b = @((New-Object PSCustomObject -Property @{'notBogus'='foo'}))
C:\Users\Rob> $b

notBogus
--------
foo

C:\Users\Rob> gm -in $b.Notbogus


   TypeName: System.String

Name             MemberType            Definition
----             ----------            ----------
Clone            Method                System.Object Clone(), System.Object ICloneable.Clone()
CompareTo        Method                int CompareTo(System.Object value), int CompareTo(string strB), int IComparab...
Contains         Method                bool Contains(string value)
CopyTo           Method                void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int co...
EndsWith         Method                bool EndsWith(string value), bool EndsWith(string value, System.StringCompari...
.
.
.

C:\Users\Rob> $b.bogus -eq $null
True

如您所见,$ b.NotBogus只是一个值,因此可以进行适当测试。

仍然不确定@tessellatingHeckler提出的反例。我会继续寻找。

答案 1 :(得分:1)

让我们从以下基本行为入手:

  • PSv3引入了member enumeration,该功能可以直接访问集合(数组)上的属性并具有 elements'属性的数组值返回。

    • ([pscustomobject] @{foo=1}, [pscustomobject] @{foo=2}).foo返回由.foo个值组成的数组,即1, 2
  • 除非有效Set-StrictMode -Version 2或更高,否则访问对象上的不存在的属性会返回$null

    • $null -eq ($PSVersionTable).Bogus产生$True
  • 具有 2个或更多元素的数组是总是真实的(在布尔上下文中的值为$True),而与元素值无关,例如,即使它们都是$null

    • [bool] ($null, $null)产生$True

因此,假设您的$coArray数组具有2个元素,即使访问伪属性也是真实的,因为if ($coArray.Bogus)实际上与if ($null, $null)相同,并且如上所述,($null, $null)

if ($null, $null) { 'true' }  # -> 'true', because the conditional is a 2+-element array

但是,奇怪的是,为伪造的属性返回$null数组似乎仅适用于自定义对象 ({[pscustomobject]个实例):

# Accessing a nonexistent property on a collection of *custom objects*
# returns a $null for each element:
PS> ([pscustomobject] @{foo=1}, [pscustomobject] @{foo=2}).Bogus.GetType().Name
Object[]  # 2-element array whose elements are $null

# By contrast, *other types* seem to evaluate to a $null *scalar* (just $null),
# as evidenced by the .GetType() call failing:
PS> ($PSVersionTable, $PSVersionTable).Bogus.GetType().Name
You cannot call a method on a null-valued expression.

this GitHub issue中讨论了这种不一致之处。