Birdman
Birdman

Reputation: 1524

Distinguish different kinds of hashtables in PowerShell?

It looks like there are different kinds of hashtables in PowerShell, ones that are case sensitive and ones that are not. When defining a hashtable as the following it's not case sensitive:

$ht = @{ "Test" = "HI" }

$ht.Contains("test") #returns true, even with key name lowercase
True

$ht.ContainsKey("test") #returns true, even with key name lowercase
True

$ht.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Hashtable                                System.Object

However, if you define it as such it is case sensitive:

$ht_caseSensitive = New-Object System.Collections.Hashtable
$ht_caseSensitive.Add("Test", "HI")

$ht_caseSensitive.Contains("test") # returns false, since it's all lowercase
False

$ht_caseSensitive.ContainsKey("test") # returns false, same with contains key function
False

$ht_caseSensitive.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Hashtable                                System.Object

However, as shown in the GetType() output, I can't seem to find a way to differentiate between these objects.

Upvotes: 4

Views: 520

Answers (1)

mklement0
mklement0

Reputation: 439247

Why is one case sensitive and the other is not?

Because PowerShell often intentionally modifies default .NET functionality to be case-insensitive, in keeping with PowerShell being case-insensitive overall.

  • Therefore, while hashtable literal @{ "Test" = "HI" } indeed creates a [hashtable] (System.Collections.Hashtable) instance, PowerShell constructs it with an explicitly case-INSENSITIVE equality comparer behind the scenes.[1]

  • By contrast, constructing a [hashtable] instance directly honors .NET's defaults, which is to simply call the .Equals() method for key lookups, which in the case of string keys (the typical case) amounts to ordinal, case-SENSITIVE comparison.


Is there any way I can differentiate between them when it's important to know whether I'm dealing with a hashtable that's case sensitive or not?

If you always use PowerShell hashtable literals and / or construct (initialize) hashtables via literal @{}, you'll always get case-insensitive lookups.


In case you still need to reflect on a given [hashtable] instance to determine whether its equality comparer is case-sensitive or not, you can use the following - obscure - technique:

# Create a case-SENSITIVE hashtable.
# Note: in PowerShell v5+, the more efficient alternative is: 
#       $someHashTable = [hashtable]::new()
$someHashTable = New-Object hashtable # System.Collections.Hashtable

# Use reflection to get the *non-public* (protected, in this case)
# .EqualityComparer property value.
$equalityComparer = [hashtable].GetProperty(
   'EqualityComparer',
   [System.Reflection.BindingFlags]'NonPublic, Instance'
 ).GetValue($someHashTable)

# The hashtable is case-sensitive if it either doesn't define
# an explicit comparer or uses one that is case-sensitive.
$isCaseSensitive = $null -eq $equalityComparer -or 
                   0 -ne $equalityComparer.Compare('a', 'A')

[1] In Windows PowerShell (versions up to v5.1), this comparer was - surprisingly - culture-sensitive; in PowerShell [Core] as of v7.0, it uses ordinal (but still case-insensitive) comparison. Note that in many other contexts PowerShell uses culture-invariant behavior instead - see this answer.

Upvotes: 5

Related Questions