5

My requirement is to store integer keys and access hash table values using those integer keys in an ordered hash table.

What works

When I use string keys, no problem:

cls

$foo=[ordered]@{}

$foo.add("12",1)
$foo.add("24",2)

write-host ("first item=" + $foo.Item("12"))
write-host ("second item=" + $foo.Item("24"))

Output:

first item=1
second item=2

Using Brackets Fails

When I use brackets, the program doesn't throw an exception, but it returns nothing:

$fooInt=[ordered]@{}

$fooInt.add(12,1)
$fooInt.add(24,2)

write-host ("first item=" + $fooInt[12])
write-host ("second item=" + $fooInt[24])

Output:

first item=
second item=

Using the Item method Fails

When I use the Item method and integer keys, PowerShell interprets the integer key as an index and not a key:

$fooInt=[ordered]@{}

$fooInt.add(12,1)
$fooInt.add(24,2)

write-host ("first item=" + $fooInt.Item(12))
write-host ("second item=" + $fooInt.Item(24))

Output:

Exception getting "Item": "Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index"
At line:8 char:1
+ write-host ("first item=" + $fooInt.Item(12))
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], GetValueInvocationException
    + FullyQualifiedErrorId : ExceptionWhenGetting

Exception getting "Item": "Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index"
At line:9 char:1
+ write-host ("second item=" + $fooInt.Item(24))
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [],  GetValueInvocationException
    + FullyQualifiedErrorId : ExceptionWhenGetting

How do I access values in a PowerShell hashtable using an integer key?

VA systems engineer
  • 2,856
  • 2
  • 14
  • 38
  • @TessellatingHeckler: nope - see my update above labeled **Using Brackets Fails** – VA systems engineer Aug 04 '18 at 14:08
  • Ah, I understand; another (worse) option is to use Reflection to pick out the right overload of `get_Item()` and call that. `$fooInt.GetType().GetMethods().where{$_.Name -eq 'get_Item' -and $_.GetParameters().name -eq 'Key'}.Invoke($fooInt, 'Public', $null, 1, $null)` – TessellatingHeckler Aug 04 '18 at 17:28

2 Answers2

9

They keys in a hashtable are objects, not strings. When you're attempting to access the key "12" with the integer 12, it cannot find that entry because the keys don't match.

HOWEVER, you're not using a standard hashtable, you're using an ordered hashtable which has a different Item method on it since it can work by key or index. If you want to access the integer key with an ordered hashtable, you need to use a different syntax:

$hash.12

If you use the array accessor syntax:

$hash[12]

it will try to return the 13th item in the list.


You can observe the difference between these objects by using Get-Member:

$orderedHash | Get-Member Item

   TypeName: System.Collections.Specialized.OrderedDictionary

Name MemberType            Definition
---- ----------            ----------
Item ParameterizedProperty System.Object Item(int index) {get;set;}, System.Object Item(System.Object key) {get;set;}

$hash | Get-Member Item

   TypeName: System.Collections.Hashtable

Name MemberType            Definition
---- ----------            ----------
Item ParameterizedProperty System.Object Item(System.Object key) {get;set;}

After some more experimentation, this is only the case on the int32 type. If you define and access it with a different type, it will work since it's no longer matching the overloaded int signature:

$hash = [ordered]@{
    ([uint32]12) = 24
}
$hash[[uint32]12]
> 24
mklement0
  • 382,024
  • 64
  • 607
  • 775
Maximilian Burszley
  • 18,243
  • 4
  • 34
  • 63
  • Thanks for the explanation - good to know the internals re: `ordered` tables and the overloads. Accessing the table using `$fooInt.12` and `$fooInt.24` worked great – VA systems engineer Aug 04 '18 at 14:13
1

Summary

$fooInt.Item([object]12) or $fooInt[[object]12]


Reasoning

As seen in TheIncorrigible1's answer, .Item has overloads; it is backed by the method get_Item:

PS C:\> $fooInt.get_Item

OverloadDefinitions
-------------------
System.Object get_Item(int index)
System.Object get_Item(System.Object key)
System.Object IOrderedDictionary.get_Item(int index)
System.Object IDictionary.get_Item(System.Object key)

The version which takes an integer and does indexing comes from the IOrderedDictionary interface, the normal IDictionary key lookup takes a [System.Object]. When you try to use it with an integer argument, PowerShell binds the version which takes an [int] because it's a better match, and runs that one.

Earlier, I made a comment of how you could use reflection to pick out the overload you want, and invoke that, but it's ugly:

$fooInt.GetType().GetMethods().where{
        $_.Name -eq 'get_Item' -and $_.GetParameters().Name -eq 'Key'
    }.Invoke($fooInt, 'Public', $null, 12, $null)
                                        ^ your parameter

Thinking on it, [int] is a value type, not a reference type, and that means .Net has to box it into an object to put it in a Hashtable. So maybe if you also box your integer parameter into an object when doing a lookup, PowerShell might bind to the overload you want and do a key lookup and still match the correct key .. what do you know, it works:

PS C:\> $fooInt=[ordered]@{}

PS C:\> $fooInt.add(12,1)
PS C:\> $fooInt.add(24,2)

PS C:\> write-host ("first item=" + $fooInt.Item([object]12))
first item=1

And it works for indexing, too:

PS C:\> write-host ("first item=" + $fooInt[[object]12])
first item=1

Which is very close to TheIncorrigible1's experiment, except you don't need to define the dictionary with the key typed as something else and then cast your lookups to a matching type, you only need to access it with casting to object, because that's happening internally already for the keys you define.

TessellatingHeckler
  • 27,511
  • 4
  • 48
  • 87
  • `[int] is a value type` technically everything is an object :shrug: also mklement0 only edited my answer ;) – Maximilian Burszley Aug 05 '18 at 02:21
  • @TheIncorrigible1 beg your pardon on the naming, I misread it as his answer edited by you, and have edited my answer to correct. If they were objects, why would they need to be boxed into objects to be used by collections, and why would generic collections have been introduced to reduce overhead by avoiding boxing? That seems rather an [argument without a clear answer](https://stackoverflow.com/questions/436211/is-everything-in-net-an-object) so I've edited the wording of my answer so it doesn't say that anymore. – TessellatingHeckler Aug 05 '18 at 20:02