Duck typing for indexed iteratables in Powershell

196 views Asked by At

Instead of piping over collections, it's sometimes more convenient to procedurally loop through them. And to avoid differentiating between $_ and $_.Key/$_.Value depending on input, a more consistent key/value handling would be nice:

ForEach-KV $object { Param($k, $v); do-stuff }

However a common type probing has its drawbacks:

#-- iterate over dicts/objects/arrays using scriptblock with Param($k,$v)
function ForEach-KV {
    Param($var, $cb, $i=0)
    switch ($var.GetType().Name) {
        Array          { $var | % { $cb.Invoke($i++, $_) } }
        HashTable      { $var.Keys | % { $cb.Invoke($_, $var[$_]) } }
       "Dictionary``2" { $var.Keys | % { $cb.Invoke($_, $var.Item($_)) } }
        PSobject       { $var.GetIterator() | % { $cb.Invoke($_.Key, $_.Value) } }
        PSCustomObject { $var.GetIterator() | % { $cb.Invoke($_.Key, $_.Value) } }
        default        { $cb.Invoke($i++, $_) }
    }
}

Apart from that one irritating type name, there's a bit much duplication here. Which is why I was looking around for duck typing in Powershell.

For hashes and objects, it's easiest/obvious to probe for .getIterator or .getEnumerator (never couldn't quite remember which belongs to which anyway):

switch ($_) {
    { $_.GetEnumerator }  { do-loopy }
    { $_.GetIterator }    { do-otherloopy }

But now I'm not quite sure what to do about arrays here. There's not that one behaviour indicator in [array]s methods that really sticks out.

  • .Get() does seem unique (at least not a method in HashTables or PSObjects), but sounds a bit too generic even for type guessing
  • .Add() might as well be an integer method(?)
  • .GetUpperBound() etc. come off as a bit too specific already.

So, is there a standard method that says "arrayish", preferrably something that's shared among other numerically-indexed value collections?

1

There are 1 answers

2
fdafadf On

If you want to match only arrays:

PS> $x = 1..10
PS> $x.GetType().IsArray
True

or you can check there is integer indexer:

(Get-Member -InputObject $x -Name 'Item' -MemberType 'ParameterizedProperty').Definition -match '\(int index\)'