タイトルは一度いってみたかっただけです、生意気言ってごめんなさい。
他の言語同様、PowerShellにも一次配列があります。こんなやつ。
PowerShellは、型を持っているのでObject[]以外にもObject[] (型の配列) などもあるのですが、他言語から見ると配列の扱いに癖があります。まとまった記事にしたことなかったので、癖(挙動を知らなければ罠に思える)についてまとめます。
- 概要
- 何がこまるの
- 罠となるポイント
- 暗黙の型変換
- 簡略化された配列宣言
- 要素の連結がオペレータによっては遅い
- 標準出力での配列型の要素が単体な場合の自動的な型変換
- オペレータの配列と単体での挙動の違い
- PowerShell がはやしてるプロパティ
- ジャグ配列内での配列維持に,が必要
- まとめ
- なんでこの記事書いたの
概要
PowerShellの一時配列は、
- 左辺に右辺が合わせられる「暗黙の型変換」
- @() を使うとTの一時配列がObjectに型変換される
- += は要素が多くなると遅くなるのでList[T]やArrayListを使ってるなら .Add() を使いましょう
@(1,2,3) -eq 1などオペレーター(-eq)の配列に対する「フィルタリング」を防止するにはヨーダ記法1 -eq @(1,2,3)を使いましょう- PowerShellがはやしてるプロパティ (.Count) には注意
- ジャグ配列内での配列維持には単項の
,オペレーターを使いましょう
何がこまるの
配列と配列の比較をしよう思ったら違った! とかいうのはよくあるんじゃないでしょうか。
配列は特によく扱うデータ型ですが、その挙動が直感とずれることが多いと「この言語なに」となるでしょう。
この記事は認識にあるずれを明確にすることで、PowerShellを思ったように扱うことを目的にしています。
罠となるポイント
経験上、PowerShellで一次配列にて困るっていうのはどれか*1に当てはまってきました。*2
「一言いう」とこうですね。
| はまりポイント | 一言 | 凶悪度*3 |
|---|---|---|
| 暗黙の型変換 | 仕方ない | 3/10 |
| 簡略化された配列宣言 | かき捨て以外は型に縛ってやる | 4/10 |
| 要素の連結がオペレータによっては遅い | 仕様が古いんです。メソッド使って | 1/10 |
| 標準出力での配列型の要素が単体な場合の自動的な型変換 | 知っててもほぼ回避できないから最悪ねっ! | 10/10 |
| オペレータの配列と単体での挙動の違い | 初見で期待する動きじゃない、生まれ変わってこい | 8/10 |
| PowerShell がはやしてるプロパティ | もはや生やしてるプロパティ使いたくない | 6/10 |
| ジャグ配列内での配列維持に,が必要 | 罠としか言えない | 9/10 |
はじめにいっておきます。PowerShellは型を持っているといいますが、基本Object | Objectにしたがります。これを覚えておいてください。*4
順番にいきます。
暗黙の型変換
これは一次配列に限りません。が、どうも混同されているケースが多いようです。それぐらい厄介なわけですね。
PowerShellは、動的型付け言語なので実行時に型が決定されます。この暗黙の型変換には原則があるのですが、型で何かしらの操作をしようと期待した時に、期待と異なる型になっていることが多いです。
.GetType()で取得できた型が期待とズレている- function (関数) で型指定で受けようとしても意図した型でないものが渡ってきてエラーになる
暗黙の型変換のルール
これだけ覚えておいてください。
実行時にオペレータの左辺(左オペランド)と右辺(右オペランド)の型が違う場合、「左辺の型に右辺の型が変化」します。
変数の型は、.GetType()メソッドで調べられるので使っていきましょう。
いくつか例を見てみます。
シンプルな型変換例
まずは簡単な例です。
一番下の$hogeの型を考えます。
この例では、左辺にSystem.String型、右辺にSystem.Int32型の変数をAppendしています。そのため、ルール通り$hogeは System.String型 となります。
変数$bがSystem.Int32型 (1) からSystem.String型 ("1")に暗黙敵型変換されたのですね。
暗黙の型変換の失敗例
先ほどの左辺と右辺を逆にしてみましょう。すると暗黙の型変換に失敗します。

先ほどと同様に、左辺のSystem.Int32型にSystem.String型を暗黙に型変換しようとしたのですね。
int -> stringは暗黙に型変換可能ですが、string -> intはできないのでエラーとなった訳です。
一次配列の型変換
さて、System.Int32型を配列に入れたいと思って、@()で括ったらどんな型になるでしょうか?
配列の中身はSystem.Int32型ままですが、配列全体はSystem.Int32[]ではなく、System.Int32[]となります。期待する型はSystem.Int32[]なので違和感があります。
実際のところ、@()は、@()と同義です。知っておけば、そんなものか。ですが、シラナイと好ましくないと感じるでしょう。
型をゆるふわにやって事故って、型をきっちりしようとしたらとても使いにくい。特に一次配列はなんでもObject[]にしたがるので扱いにくさが目についてしまいます。
回避策
左辺に合わせて右辺の型は暗黙に変換される。これを覚えておきましょう。インテリセンスでこういうの指摘してほしいですよね。
ScriptAnalyzerでの静的な解析わんちゃんですね。
PowerShell Tools for Visual Studioは... ほげ。現状打つ手なしなのでPR投げてください。現在はMicrosoftも開発に参加して実装速度が上がってます。
簡略化された配列宣言
次は配列宣言です。
PowerShellで配列を宣言する方法はいくつかあります。
よくある簡略な方法
一番多く表現される方法は先ほどの、@()でしょう。
先ほど説明した通り、System.Object[]になるので注意です。
明示的な宣言
他言語では、C# でいうvarのような型推論を使っていてもインテリセンスがあるので困りません。しかし、PoweShellやPowerShell ISEのインテリセンスは型サポートが貧弱なのでさもすると型を把握できなくなります。さっきの例がvarになるのはその例です。
こういうときは、型を明示しておくと安心できるでしょう。(このあと説明する要素が単数の場合を除いては)
単数を一次配列にする
これがはまりどころでもあり、回避策です。
1はSystem.Int32型ですが、@(1)はSystem.Object[]型です。@(1)のように出力を囲むと配列扱いになります。
@()で配列とするのは効果的ですが、型的には困ったものです。
要素の連結がオペレータによっては遅い
配列に要素を追加するときにどうしていますか?
もしかして、+=を使ってませんか? 要素が増えたときに死にますよ、それ。
回避策
ジェネリクスの List[T] を使ってるなら.Addメソッドをつかってください。廃棄物のArrayListでも同様です。
詳しくは以前まとめたのでどうぞ。
標準出力での配列型の要素が単体な場合の自動的な型変換
私が考えるPowerShellの配列の罠で断然トップはこれです。えげつなさヤバイ。
この挙動地雷以外のなんて呼べばいいんですかね。一次配列は結果が単数の場合、配列ではなくなってしまいます。*5
回避策
ここで、先ほどの単数を配列にする という@()が現れます。*6
はい。
オペレータの配列と単体での挙動の違い
ここでいうオペレータは特に-eq | -eq演算子を指します。
このオペレータ、比較判定しているつもりが、実はフィルタリングをしています。
配列にやってみましょう。
配列1,2,3,4,5,6,7,8,9,10は、1とは違うので期待する動作はfalseが返ってくることですが、まさかの1が返ってきます。
これは、-eqオペレータが配列に対してはフィルタオペレータだからです。そのため、左辺の1..10の中に1が含まれていたので、該当した1がとりだされました。
if文の中でやっていると、 フィルタ結果の1がが[bool]1となってtrueと判定されます。
では、右辺が配列の左辺に含まれないものだった場合は?
結果は空です。しかしこの結果がまた厄介です。
なんとか判定したくても、さて。
細かい解説ははぐれメタルセンセーを。
nullに関しては、上記ブログだけでは気づきにくいパターンもあります。例えばこれ、結果に気づけましたか?
回避策
原則ルールを思いだしてください。左辺に暗黙の型変換されるのです。そして配列に対してでなければ、-eqは比較判定に使えます。
判定したい値を左辺に持ってくるのが回避策になります。
nullも左辺に持ってきましょう。
PowerShell がはやしてるプロパティ
PowerShellは、.NET標準の型に独自のプロパティやメソッドを生やしています。*7
CodeProperty、ScriptProperty、NoteProperty、AliasProperty などとあったら.NET標準ではなくPowerShellが生やしているプロパティです。
TypeName: System.Int32
Name MemberType Definition
---- ---------- ----------
pstypenames CodeProperty System.Collections.ObjectModel.Collection`1[[System.String, mscorlib, Version=4.0.0.0, Culture=ne...
psadapted MemberSet psadapted {CompareTo, Equals, GetHashCode, ToString, GetTypeCode, GetType, ToBoolean, ToChar, ToS...
psbase MemberSet psbase {CompareTo, Equals, GetHashCode, ToString, GetTypeCode, GetType, ToBoolean, ToChar, ToSByt...
psextended MemberSet psextended {}
psobject MemberSet psobject {BaseObject, Members, Properties, Methods, ImmediateBaseObject, TypeNames, get_BaseObjec...
CompareTo Method int CompareTo(System.Object value), int CompareTo(int value), int IComparable.CompareTo(System.Ob...
Equals Method bool Equals(System.Object obj), bool Equals(int obj), bool IEquatable[int].Equals(int other)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
GetTypeCode Method System.TypeCode GetTypeCode(), System.TypeCode IConvertible.GetTypeCode()
ToBoolean Method bool IConvertible.ToBoolean(System.IFormatProvider provider)
ToByte Method byte IConvertible.ToByte(System.IFormatProvider provider)
ToChar Method char IConvertible.ToChar(System.IFormatProvider provider)
ToDateTime Method datetime IConvertible.ToDateTime(System.IFormatProvider provider)
ToDecimal Method decimal IConvertible.ToDecimal(System.IFormatProvider provider)
ToDouble Method double IConvertible.ToDouble(System.IFormatProvider provider)
ToInt16 Method int16 IConvertible.ToInt16(System.IFormatProvider provider)
ToInt32 Method int IConvertible.ToInt32(System.IFormatProvider provider)
ToInt64 Method long IConvertible.ToInt64(System.IFormatProvider provider)
ToSByte Method sbyte IConvertible.ToSByte(System.IFormatProvider provider)
ToSingle Method float IConvertible.ToSingle(System.IFormatProvider provider)
ToString Method string ToString(), string ToString(string format), string ToString(System.IFormatProvider provide...
ToType Method System.Object IConvertible.ToType(type conversionType, System.IFormatProvider provider)
ToUInt16 Method uint16 IConvertible.ToUInt16(System.IFormatProvider provider)
ToUInt32 Method uint32 IConvertible.ToUInt32(System.IFormatProvider provider)
ToUInt64 Method uint64 IConvertible.ToUInt64(System.IFormatProvider provider)
特にGet-Processなどはわかりやすいでしょう。試してみてください。
細かく知りたい人へ
id:aetos382さんの神まとめを見ればなんとなく理解が深まるでしょう。
罠になりやすい例
PowerShellで、配列の長さを取るときに、良く用いられるのが謎の.Countプロパティです。
でもこれ、LengthプロパティのLengthとしてPowerShellが配列にのみ設定しているんですね。

そのため、配列じゃない型には存在しません。

ここで、標準出力での配列型の要素が単体な場合の自動的な型変換 を思い出してください。そう、結果を@()で囲んで配列に強制変換する謎手法を使わないと、.Countが取れない! という状況になったりするんですね。*8
回避策
私あまり@()好きじゃないので、結果が少ないとわかっているなら@()で数えることが多いです。
.Lengthプロパティアクセスと比べてコストが高いので嫌なんですが、数が少なければ誤差なので。
あるい型をしっかり配列か確認して、Lengthプロパティを使うといいでしょう。
ジャグ配列内での配列維持に,が必要
ジャグ配列は、その配列要素も配列である配列です。つまり@(@(1,2,3),@(4,5,6)) のようなものです。この例のジャグ配列要素へのインデックスアクセスはイメージ通りにできます。
@(@()) は @() というまさかの罠
しかし、PowerShellは、@(@(<配列>))は@(@(<配列>))とみなします。つまりこうなっちゃいます。
解決策
そこで利用するのが単項配列演算子の,です。,を対象配列の前に置くことでジャグ配列でも配列を維持します。
厄介ですね!
まとめ
どれも知っていれば回避はできます。そういう問題ではない? そうですか。
まぁ気軽に2,3行書いたり、シェル上でCmdletを1つ実行するだけのシーンが多いと楽なんでしょうねぇ。*9
なんでこの記事書いたの
罠が多いという声がおおいので書けという天の声が聞こえました。