Search code examples
powershellpowershell-4.0

Why does an empty Powershell hash table evaluate to True when an empty array evaluates to False?


I'm experimenting with True and False in Powershell 4.0. Here are the results when looking at an empty array and an empty hash table:

PS C:\Users\simon> $a = @()    
PS C:\Users\simon> if ($a) { Write-Host "True" } else { Write-Host "False" }
False

PS C:\Users\simon> $a = @{}    
PS C:\Users\simon> if ($a) { Write-Host "True" } else { Write-Host "False" }
True

Why do empty hash tables evaluate to True when empty arrays evaluate to False?

According to the Powershell documentation About Comparison Operators:

When the input is a collection of values, the comparison operators return any matching values. If there are no matches in a collection, comparison operators do not return anything.

From that I would expect both a hash table and an array to act the same when they are empty, since both are collections. I would expect both to evaluate to False because they return nothing to the if clause.


Solution

  • This isn't a question of True/False. You can test this using the Boolean operators $true and $false. I've used $h as the empty hashtable @{}

    PS C:\> $a = @()
    PS C:\> $h = @{}
    PS C:\> if ($a -eq $true) { Write-Host "True" } else { Write-Host "False" }
    False
    
    if ($h -eq $true)    >  False
    if ($a -eq $false)   >  False
    if ($h -eq $false)   >  False
    

    Also not equal to automatic variable $null:

    if($a -eq $null)     >  False
    if($h -eq $null)     >  False
    

    As per iRon's link, Count is a better test to see if a hashtable/array is empty. Related Length

    The different behaviour will be related to the fundamental types.

    PS C:\> $a.GetType()
    
    IsPublic IsSerial Name                                     BaseType
    -------- -------- ----                                     --------
    True     True     Object[]                                 System.Array
    
    PS C:\> $h.GetType()
    
    IsPublic IsSerial Name                                     BaseType
    -------- -------- ----                                     --------
    True     True     Hashtable                                System.Object
    

    It's to do with how the if statement works and the Length property, AFAICS. Below is my (admittedly shaky) understanding from multiple StackOverflow posts and external links.


    Get-Member behaviour is different - see Mathias's explanation

    $a | Get-Member            # Error - Get-Member : You must specify an object for the Get-Member cmdlet.
    $h | Get-Member            # TypeName: System.Collections.Hashtable. Has Count method, no Length property
    Get-Member -InputObject $a # TypeName: System.Object[]. Has Count method, has Length property
    

    The Length property of the variables is different. But the hashtable does not have a Length property - see mjolinar's answer.

    $a.Length   > 0
    $h.length   > 1
    

    Combining with Ansgar's link explains the different behaviors in the if statement as it seems to be implicitly getting the Length property . It also allows us to do this:

    if ($a.Length -eq $true)    >  False
    if ($h.Length -eq $true)    >  True
    

    Using the IsNullOrEmpty method of the [String] .Net class gives different output compared to $null, I assume because this also relies on the Length.

    if ([string]::IsNullOrEmpty($a))    > True
    if ([string]::IsNullOrEmpty($h))    > False