PowerShellで配列を操作する際に割とハマりがち? な気がする。
参照だったりShallowCopyだったりDeepCopyの話。
参照? Shallo Copy? Deep Copy?
まず前提として。
- PowerShellで配列を代入した場合は参照がコピーされる
- PowerShellで配列のcloneメソッドを利用するとShallowコピーされる
これはどういうことかというとサンプルをやってみるのが早い。
代入すると参照
# 元となる配列の宣言
$fromVariable=@("one","two","three","four","five")
# fromVariableをtoVariable変数に代入
$toVariable=$fromVariable
# toVariable変数を変更
$toVariable[0]="hello"
$toVariable[1]="world"
# 配列を表示してtoVariableへの変更がfromVariableに反映されていることの確認
# hello world three four five と表示
$fromVariable | %{ $_ }
# hello world three four five と表示
$toVariable | %{ $_ }
上記のようにPowerShellでは配列を変数に代入すると参照がコピーされます。
参照がコピーされるので、参照先を変更すると参照元も変更されます。
Cloneメソッドを利用するとshallow Copy
PowerShellの配列にはCloneメソッドが実装されています。
これを利用するとshallow Copyされます。
# 元となる配列の宣言
$fromVariable=@("one","two","three","four","five")
# toVariable変数に代入
$toVariable=$fromVariable.clone()
# toVariable変数を変更
$toVariable[0]="hello"
$toVariable[1]="World"
# 配列を表示してtoVariableへの変更がfromVariableに反映されていることの確認
# one two three four five
$fromVariable | %{ $_ }
# hello world three four five
$toVariable | %{ $_ }
先ほどの代入とは異なり、shallowCopy(簡易コピー)なので更に参照を含まないようなフラットな配列では。
参照先(toVariable)への変更が参照元(fromVariable)には影響していない事がわかります。
ただし下記のような配列の中で更に参照している場合は、shallowCopy(簡易コピー)なので参照先(toVariable)への変更が参照元(fromVariable)へ影響してしまいます。
$foobar=[pscustomObject]@{"foo"="bar"}
# 元となる配列の宣言
$fromVariable=@($foobar,"hello","world")
# toVariable変数にshallowCopy
$toVariable=$fromVariable.clone()
# toVariable配列の先頭にあるpscustomObjectの書き換え
$toVariable[0].foo= "helloworld"
# 配列を表示してtoVariableへの変更がfromVariableに反映されていることの確認
# @{foo=helloworld} hello world ※元々は@{foo=bar} だったのが書き換えられている。
$fromVariable | %{ $_}
# @{foo=helloworld} hello world
$toVariable | %{ $_}
Deep Copyしたい
配列の中に更に参照を含むような入れ子になっている配列について。
deep copyしたい場合のトリック。
Deep copy arrays in PowerShell
上記ページに記載のあるように、シリアライズしてからデシリアライズするというトリックでほぼ対応できるようです。
$rawFoobar=[pscustomObject]@{"foo"="bar"}
$foobar=$rawFoobar
# 元となる配列の宣言
$fromVariable=@($foobar,"hello","world")
# toVariable変数にshallowCopy
$toVariable=[Management.Automation.PSSerializer]::DeSerialize([Management.Automation.PSSerializer]::Serialize($fromVariable))
# toVariable配列の先頭にあるpscustomObjectの書き換え
$toVariable[0].foo= "helloworld"
# 配列を表示してtoVariableへの変更がfromVariableに反映されていることの確認
# @{foo=bar} hello world
$fromVariable | %{ $_}
# @{foo=helloworld} hello world
$toVariable | %{ $_}
ほぼ対応できる?
参照元のページにほぼ対応できると記載があり、Bigintについて言及されています。
たしかにbigintが含まれる配列をこの手法で使うと下記のようになりました。
# bigintを含む配列を用意
$fromVariable=@([bigint]100,[int]200,[int]300)
# deep copy
$toVariable=[Management.Automation.PSSerializer]::DeSerialize([Management.Automation.PSSerializer]::Serialize($fromVariable))
# typeをチェック
$fromVariable | %{ $_.gettype() }
# typeをチェック
$toVariable | %{ $_.gettype() }
#比較してみる
$fromVariable[0] -eq $toVariable[0]
$fromVariable[1] -eq $toVariable[1]
$fromVariable[2] -eq $toVariable[2]
画像のようにbigintがpsobjectになっていることがわかります。
総評
個人的にはPowerShellで配列を扱っている時に、知らずやらかしがちな部分かとは思います。