4

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.

  • Why is one case sensitive and the other is not?
  • 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?
Birdman
  • 1,404
  • 5
  • 22
  • 49
  • 1
    By default PS hashtables are case insensitive. ONLY by defining the object as in your 2nd example or a couple of very rare methods e.g. export/importing xml can they be made case sensitive. – Scepticalist Mar 08 '20 at 19:43
  • 2
    This is also a PSScriptAnalyzer rule: [UseLiteralInitializerForHashtable](https://github.com/PowerShell/PSScriptAnalyzer/blob/development/RuleDocumentation/UseLiteralInitializerForHashtable.md) – iRon Mar 08 '20 at 21:07

1 Answers1

5

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.

mklement0
  • 382,024
  • 64
  • 607
  • 775