新しいクラスが作りたい理由
PowerShell を使っていて時々不便に感じるのが、新しいクラスを作れないことです。
たとえば、System.Collections.ArrayList に CSV ファイルを読み込み、任意の列でソートしようと思えば、 IComparer インターフェースを実装するクラスを新しく作らなくてはなりませんが、その手段がありません。.NET Framework は、新しいクラスが簡単に作れることを前提に設計されていたので、それができないと、思わぬところでつまづくことがあります。
一番頻繁に感じるのが、新しいデリゲートクラスが作れないことでしょう。デリゲートはありとあらゆる場所で、便利な、あるいは必須のコールバックとして使われています。
また、クラスが作れないことによって、Win32API を呼び出すような PInvoke メソッドが実装できない困難もあります。
もちろん、逃げ道もあります。コマンドレットを高級言語で実装する方法や、デリゲートならば、「Windows PowerShell で delegate」 のような方法、その他も 「PowerShell で C# の実行」 や、 「メモリリークのない C# の実行」 のように、他の言語をコンパイルして実行することでどうにかなります。
しかし、それでも「新しいクラスが簡単に作れたらなあ」と思うことはしばしばです。「思うならできるようにしよう」ということで作ったのが、次のスクリプトです。
ダウンロード
- 新しいクラスを作るスクリプト。
New-Class.ps1
このスクリプトでできること
- 新しいクラスの作成
- コンストラクタ・メソッド・プロパティ・フィールドをスクリプトブロックで実装
- インターフェースの追加
- PInvoke メソッドの定義
使用法
New-Class.ps1 の使用法については、これから段階を追って説明していきたいと思います。今回は簡単に、System.Object から何の機能追加もなしに継承したクラスを作ります。 次のように、「New-Class <文字列> <スクリプトブロック>」のように起動し、「文字列」には新しいクラスの名前を、「スクリプトブロック」にはクラスの定義を指定します。今回は機能追加しないので、スクリプトブロックの中身は空です。
PS> New-Class Flamework.Object {} | ||
NameSpace | Name | BaseType |
———– | —- | ———- |
Flamework | Object | System.Object |
新しいクラスができたので、オブジェクトを作ってみましょう。
PS> $o.ToString()
Flamework.Object
PS> $o.GetType() -eq [System.Object]
False
ちゃんとできました。
実装
このスクリプトは、メモリ内に動的アセンブリを定義し、System.Reflection.Emit.TypeBuilder で新しい型を作成しています。
「動的アセンブリはメモリリークの原因になると言ったじゃないか!」ですって?確かに、スクリプトを起動するたびに動的アセンブリを作っていたのでは、使わないアセンブリがどんどんたまっていきます。しかし、このスクリプトは、二回目以降の起動では、前回のアセンブリを再利用します。また、一度クラスを作ると、PowerShell が終了するまで同じ名前で作ることはできないので、同じ名前、同じ機能で異なるバージョンのクラスが次々に作られることもありません。
クラスの実装には System.Reflection.Emit.ILGenerator によって得られた IL を使い、各メソッドの内部からスクリプトブロックを起動しています。