ブログ「サイバー少年」

ブログ「サイバー少年」へようこそ!
小学六年生ごろからプログラミングを趣味にしている高校生のブログです。
勉強したことについての記事などを書いています。フリーソフトも制作、公開しています。
(当ブログについて詳しくは「ブログ概要紹介」を参照)

サイバー少年が作ったフリーソフトは「サイバー少年の作品展示場」へ

PowerShellでジェネリックス!

またPowerShellの記事ですが、今回はいつものように長々と書くのではなく、当ブログではすごく久しぶりにメモ程度の記事です。

…と思っていたら、またまた長くなってしまった。
短くまとめられる能力が欲しいですね。


PowerShellでは型を意識しない書き方をできるようになっていますが、ジェネリックスを相手にする場合は型を明示しなければなりません。

そこで、今回はジェネリッククラスとジェネリックメソッドをどのように呼び出せばよいか書きます。

実は私も昔、何回か気にした問題なのですが、その度に忘れるので今回こそメモします。



ジェネリッククラス

まず知っていただきたいのは、型引数をn個持つジェネリッククラスは、PowerShellにおいて本当の名前はその型名に'`n'をくっつけたものであるということです。

つまりは、'System.Collections.Generic.List<T>'とC#で呼ばれているものは、PowerShellでは'System.Collections.Generic.List`1'になって、
'System.Collections.Generic.Dictionary<TKey, TValue>'なら、'System.Collections.Generic.Dictionary`2'となります。


しかし実際のところPowerShellでは、型名を書いたあと型引数を続けて書かなければならないので、そこから推論することが可能です。
つまりは、この数字を後につけることは、あまりないと思います。


静的クラス

まずは静的メンバにアクセスするために、ジェネリッククラス名を書く場合の方法です。
まず型名を[]で囲むわけですが、さっき実験して驚いたことがありまして、

PowerShellの以前のバージョンでは型名を[]で囲んだものを書いてエンターを押しても、別にメソッドを呼んでいるわけでもないのでエラーになっていました。

しかし、先ほどv5.0で実験してみたところ、[]で囲んだ型名だけでも、それはSystem.Typeのインスタンスとして解釈されるようになったようです。

そして[]の後に書く::は、左の[]と結びついている記号じゃなくなり、
左のSystem.Typeが表す型に定義されている静的メンバにアクセスする記号という意味に変わったということです。

これが何を意味するかというと、

$type = '文字列'.GetType()
$type::FullName


こういう変数を介したアクセスが可能になったということです。


さて、本題に戻しますが、要するに話はジェネリッククラスのSystem.Typeをどのように作るかという問題になります。

これは、すごく単純に、まず型名を書いて、そのあと任意で前述の'`n'を付加します。

そして、そのあと[]を付加して、この中に型引数を書きます。
型引数が複数あるなら、','で区切ります。

そして、この時点で型名を書くことができたということになるのですが、ここからSystem.Typeを作るために、全体を[]で囲みます。

よってたとえば、'[System.Collections.Generic.List[string]]'とか、'[System.Collections.Generic.Dictionary[int,string]]'ということになります。

あとは::を書いて普通にメンバ名を書くだけです。



インスタンス化

続いて、インスタンス化するためにジェネリッククラス名を書く場合の方法です。

これは、先ほど型名を書いたあと全体を[]で囲んでSystem.Typeのインスタンスにしたわけですが、

この全体を[]で囲むというのをやめれば、単純な型名になるので、基本的にはそれだけのことです。


ただ、問題なのは、New-Object (型名)と普段書いているわけですが、これは実はNew-Objectのあとに型名の文字列のstringを渡しているだけで、
PowerShellの文法チェッカーが介入しているものではないということです。

ようするに、New-Objectが必要としているのは型名ではなくstringの引数です。
なので、New-Objectの後に型名を書いてエンターをすると、その型名をPowerShellがstringとして解釈を試みるのですが、

New-Object System.Collections.Generic.List[string]
みたいに型引数が1つなら、なんか大丈夫みたいなんですが、

New-Object System.Collections.Generic.Dictionary[int,string]
みたいに型引数が複数あると、
どういう仕様になっているのかよく分からないのですが、これは型名の部分をstringとして認識してくれないようです。


なので、型引数が複数ある場合はこのやり方では無理ですけども、考えてみれば、構文をどう解釈するかという問題でしかないのであって、

ようするに明示的に型名をクォーテーションで囲って、stringとしてNew-Objectに渡せばいいだけのことです。


つまり
New-Object 'System.Collections.Generic.Dictionary[int,string]'
とすれば、ちゃんと動きます。

というわけでシングルまたはダブルのクォーテーションで囲みましょう。


また先ほど、型名の後に任意で'`n'を付けてもいいと書きましたが、シングルクォーテーションで型名を囲むなら無問題ですけども、
ダブルクオーテーションで囲っていた場合は、'`'は文字参照の記号になるので、上手いこといきません。


インスタンス化(2)

インスタンス化の別の方法として、PowerShellではクラスに静的メソッドnewが定義されていて、これがコンストラクタであるということになっているので、それを呼ぶ方法もあります。


前述したような静的メンバにアクセスする方法で

[System.Collections.Generic.List[int]]::new()
みたいにすれば、これでもインスタンスが返ってきます。



ジェネリックメソッド

ジェネリックメソッドの項をでかでかと作っておいてなんなんですが、
PowerShellでは素直なやり方でジェネリックメソッドを呼ぶことはできないようです。


ただ一部の例外として、PowerShellでもC#と同じように、メソッド引数の型が型引数を使用していた場合は、
呼び出す際はメソッド引数を与えた時点で、メソッド引数の型から型引数を推論してくれます。

これを利用して、型引数を直接指定することなく推論で決定できる場合のみ、普通にメソッド引数を与えるだけでジェネリックメソッドを呼ぶことができます。


それ以外の場合は、PowerShellの文法の範疇では不可能だと思います。

まあ、ここでは書きませんが、System.Typeをいじることで呼ぶことはできるようです。

そこまで面倒なものでもないっぽいので、英語ばっかりですが気になったらググってみてください。


今後のPowerShellのアップデートで、PowerShellの機能としてジェネリックメソッドを呼び出せるようにしてほしいですね~。



というわけで、PowerShellのジェネリックスの扱いは少し足りていない部分もあるんですが、まあ仕様変更も激しいようなので期待していましょう。

とりあえず現段階としては、この記事がお役に立てれば幸いです。

なにより私自身が、後々読み返すことになると思います
(笑)

tag: PowerShell スクリプト クラス メソッド ジェネリックス 文法 推論

コメント

ありがとう 参考になりました

  • 2017/05/11(木) 15:03:01 |
  • URL |
  • 表記なし #-
  • [ 編集 ]

コメントの投稿

トラックバック

トラックバック URL
http://cyberboy6.blog.fc2.com/tb.php/456-0ed823ef
この記事にトラックバックする(FC2ブログユーザー)

当ブログをご利用(閲覧等)になる場合は必ず「当ブログの利用規定」をお守りください。