hashtable をKey-Valueのコレクションとして使いたい
のですが、普通にforeach, ForeEach-Object しただけでは期待した動作になりませんでした。
- 期待する結果
PS C:\> $items = @{aaa=111;bbb=222;ccc=333} PS C:\> $items | ForEach-Object {"{0}={1}" -f $_.Key, $_.Value} aaa=111 bbb=222 ccc=333
- 実際は
PS C:\> $items = @{aaa=111;bbb=222;ccc=333} PS C:\> $items | ForEach-Object {"{0}={1}" -f $_.Key, $_.Value} =
- 要素が列挙されず、Hashtableがそのまま渡されてる!
PS C:\> $items | ForEach-Object {$_.GetType().FullName} System.Collections.Hashtable
- System.Collrctions.IEnumerableが実装されているはずですよね?
PS C:\> $items.GetEnumerator().GetType().FullName System.Collections.Hashtable+HashtableEnumerator
IEnumerable.GetEnumerator はちゃんとあります。
- Enumeratorでforeachしてみる?
PS C:\> $items.GetEnumerator() | ForEach-Object {$_.GetType().FullName} System.Collections.DictionaryEntry System.Collections.DictionaryEntry System.Collections.DictionaryEntry
あ!できた?
PS C:\> $items.GetEnumerator() | ForEach-Object {"{0}={1}" -f $_.Key, $_.Value} aaa=111 bbb=222 ccc=333
おおお!できました。
- でもGetEnumerator()つけるのなんかダサい気がする
調べてみると、以下のような書き方が一般的のようですね。
Hashtableをforeachしても・・・ - PowerShell Scripting Weblog
PS C:\> $items.Keys | ForEach-Object {"{0}={1}" -f $_, $items[$_]} bbb=222 ccc=333 aaa=111
コマンドレットにパイプで渡すと?
- テスト用のfunctionで見てみましょう
function Test-Parameter { [CmdletBinding()] Param( [Parameter(ValueFromPipeLine=$true, Mandatory=$true)] [PSObject] $InputObject ) Begin { "*** Begin ***" } Process { "*** Process ***" " - InputObject: {0}" -f $InputObject.GetType() if($InputObject -is [System.Collections.IEnumerable]) { " - foreach {" foreach($o in $InputObject){" {0}={1}" -f $o.GetType(),$o} " }" } else { " - InputObject: {0}={1}" -f $InputObject.GetType(),$InputObject } } End { "*** End ***" } }
PS C:\> $items = @{aaa=111;bbb=222;ccc=333} PS C:\> $items | Test-Parameter *** Begin *** *** Process *** - InputObject: System.Collections.Hashtable - foreach { System.Collections.Hashtable=System.Collections.Hashtable } *** End ***
想像通りですが、パイプで渡しても列挙されずに、ProcessにはHashtableが1回だけ入ってきました。
- GetEnumerator()してから渡してみる
PS C:\> $items.GetEnumerator() | Test-Parameter *** Begin *** *** Process *** - InputObject: System.Collections.DictionaryEntry - InputObject: System.Collections.DictionaryEntry=System.Collections.DictionaryEntry *** Process *** - InputObject: System.Collections.DictionaryEntry - InputObject: System.Collections.DictionaryEntry=System.Collections.DictionaryEntry *** Process *** - InputObject: System.Collections.DictionaryEntry - InputObject: System.Collections.DictionaryEntry=System.Collections.DictionaryEntry *** End ***
ちゃんと要素数だけ DictionaryEntry が渡されてきました。
コレクションを処理するコマンドレットに、Hashtableをコレクションとして渡したいケースがあれば、 Hashtable.GetEnumerator()
で渡すのもアリかも。
他にも
string 型もIEnumerable<char>
を実装していますが、そのままでは列挙しません。
PS C:\> "abcdefg" | ForEach-Object {"{0}:{1}" -f $_.GetType().FullName,$_} System.String:abcdefg PS C:\> "abcdefg".GetEnumerator() | ForEach-Object {"{0}:{1}" -f $_.GetType().FullName,$_} System.Char:a System.Char:b System.Char:c System.Char:d System.Char:e System.Char:f System.Char:g
まあ、string をchar配列として使いたいケースは稀ですし、使いたい場合はToCharArray()で変換可能ですので、あまり気にする必要はないかも。
PS C:\> "abcdefg".ToCharArray() | ForEach-Object {"{0}:{1}" -f $_.GetType().FullName,$_} System.Char:a System.Char:b System.Char:c System.Char:d System.Char:e System.Char:f System.Char:g