2007/11/27 火曜日

新しいクラスを作る(2) ― フィールド

Filed under: Powershell — flamework @ 18:23:16

概要

前回の 新しいクラスを作る(1)でご紹介した New-Class.ps1を使って、フィールドを持つクラスを作ってみます。

フィールドの実装

新しいクラス「Class1」を作成し、string 型のパブリックフィールド「Message」を実装してみましょう。

PS> New-Class Class1 {Field public string Message}
NameSpace Name BaseType
----------- ---- ----------
  Class1 System.Object

オブジェクトを作って Get-Member に渡してみます。

PS> $o = New-Object Class1
PS> $o | Get-Member
 
TypeName: Class1
 
Name MemberType Definition
---- ---------- ----------
Equals Method System.Boolean Equals(Object obj)
GetHashCode Method System.Int32 GetHashCode()
GetType Method System.Type GetType()
ToString Method System.String ToString()
Message Property System.String Message {get;set;}

確かに「Message」というメンバが増えています。「Property」と表示されていますが、これは PowerShell での表示上の問題です。別の方法で確かめてみましょう。

PS> [Class1].GetProperties()
PS> [Class1].GetFields()
System.String Message

GetProperties() では何も表示されませんが、GetFields() で「System.String Message」と表示され、確かにフィールドであることがわかります。 実際に使ってみましょう。

PS> $o.Message = 'Hello World!'
PS> $o.Message
Hello World!

Field コマンドの解説と静的メンバの実装

クラスメンバは、スクリプトブロックの中にコマンドを記述することで定義します。上記では、「Field public string Message」というコマンドを与え、フィールドを実装しました。まず、「Field」というのが、「フィールドを作成する」というコマンドです。続く「public」「string」「Message」が、その引数で、それぞれ「属性」「型」「名前」をあらわします。 属性は、「public」の他に、「private」「static」が使えます。属性を 2 つ以上使いたい場合には、「('public', 'static')」のように、文字列の配列にして渡します。 それでは、Message フィールドを静的フィールドにしてみましょう。

PS> New-Class Class2 {Field ('public', 'static') string Message}
NameSpace Name BaseType
----------- ---- ----------
  Class2 System.Object

試してみます。

PS> [Class2]::Message = 'Hello World!'
PS> [Class2]::Message
Hello World!

これでフィールドが作れるようになりました。

新しいクラスを作る(1)

Filed under: Powershell — flamework @ 17:09:32

新しいクラスが作りたい理由

PowerShell を使っていて時々不便に感じるのが、新しいクラスを作れないことです。

たとえば、System.Collections.ArrayList に CSV ファイルを読み込み、任意の列でソートしようと思えば、 IComparer インターフェースを実装するクラスを新しく作らなくてはなりませんが、その手段がありません。.NET Framework は、新しいクラスが簡単に作れることを前提に設計されていたので、それができないと、思わぬところでつまづくことがあります。

一番頻繁に感じるのが、新しいデリゲートクラスが作れないことでしょう。デリゲートはありとあらゆる場所で、便利な、あるいは必須のコールバックとして使われています。

また、クラスが作れないことによって、Win32API を呼び出すような PInvoke メソッドが実装できない困難もあります。

もちろん、逃げ道もあります。コマンドレットを高級言語で実装する方法や、デリゲートならば、「Windows PowerShell で delegate」 のような方法、その他も 「PowerShell で C# の実行」 や、 「メモリリークのない C# の実行」 のように、他の言語をコンパイルして実行することでどうにかなります。

しかし、それでも「新しいクラスが簡単に作れたらなあ」と思うことはしばしばです。「思うならできるようにしよう」ということで作ったのが、次のスクリプトです。

ダウンロード

このスクリプトでできること

  • 新しいクラスの作成
  • コンストラクタ・メソッド・プロパティ・フィールドをスクリプトブロックで実装
  • インターフェースの追加
  • PInvoke メソッドの定義

使用法

New-Class.ps1 の使用法については、これから段階を追って説明していきたいと思います。今回は簡単に、System.Object から何の機能追加もなしに継承したクラスを作ります。 次のように、「New-Class <文字列> <スクリプトブロック>」のように起動し、「文字列」には新しいクラスの名前を、「スクリプトブロック」にはクラスの定義を指定します。今回は機能追加しないので、スクリプトブロックの中身は空です。

PS> New-Class Flamework.Object {}
NameSpace Name BaseType
----------- ---- ----------
Flamework Object System.Object

新しいクラスができたので、オブジェクトを作ってみましょう。

PS> $o = New-Object Flamework.Object
PS> $o.ToString()
Flamework.Object
PS> $o.GetType() -eq [System.Object]
False

ちゃんとできました。

実装

このスクリプトは、メモリ内に動的アセンブリを定義し、System.Reflection.Emit.TypeBuilder で新しい型を作成しています。

動的アセンブリはメモリリークの原因になると言ったじゃないか!」ですって?確かに、スクリプトを起動するたびに動的アセンブリを作っていたのでは、使わないアセンブリがどんどんたまっていきます。しかし、このスクリプトは、二回目以降の起動では、前回のアセンブリを再利用します。また、一度クラスを作ると、PowerShell が終了するまで同じ名前で作ることはできないので、同じ名前、同じ機能で異なるバージョンのクラスが次々に作られることもありません。

クラスの実装には System.Reflection.Emit.ILGenerator によって得られた IL を使い、各メソッドの内部からスクリプトブロックを起動しています。

« 前ページへ

Copyright © flamework.net 2008.