tech.guitarrapc.cóm

Technical updates

PowerShell で 関数実行結果の型をパイプラインの先に伝搬する

PowerShell といえば、型です。

文字列じゃなくて型。ということは、インテリセンスが何を使えるか教えてくれるわけです。インテリセンス使えないと辛いですよね。

自作の関数を作ったことがある人は多いと思います。でも、作った関数の出力結果がパイプラインの先でインテリセンス効かないって経験ありませんか?

今回はどうしてなのか、どうやって解決するのかを説明します。

目次

問題はどういうこと

例えば、[System.String]を返すだけの単純な関数を考えてみます。

ではこの関数を実行した結果を、パイプラインの先に伝搬してみましょう。

結果は?自動変数$_.ピリオドを入力してもインテリセンスがききません。しょぼーん。

f:id:guitarrapc_tech:20150120031501p:plain

つまり、さくっと関数を書いても、パイプラインの先にオブジェクトが渡るまで型をインテリセンスでサポートしてくれないわけです。

出力型を明示する

これは C# でCmdlet を書いていると気づきません。C# なら返戻値の型を明示するからです。

しかし、PowerShell スクリプトで関数を書いていると逆に返戻値の型を明示することを失念しがちです。そう、返戻値の型を明示してインテリセンスを助けてないから、パイプラインの先で型がわからないのです。

対応方法

高度な関数(でしたっけ? Advanced Function とか言われたりする奴ですね) の中に答えがあります。

返戻値の型を明示するには、[OutputType()]属性を付ければ ok です。*1*2

コードで示しましょう。

これで先ほどと同様にパイプラインの先に型が伝搬したかみてみると?

自動変数$_ の後に ピリオド. を入力した瞬間、うまく型がインテリセンスに表示されましたね。

f:id:guitarrapc_tech:20150120032332p:plain

[OutputType()] 属性のいけてなさ

意味があったのに余り使われていないのには、知られてないノか欠点が目立ってるからなのか。

いけてないところを見てみます。

返戻値の型がずれても何も警告もエラーも出ない

C# なら、返戻値の型と実際の型がずれることはVSで警告を出して起こることがないですね。

しかし、PowerShell ISE というか、PowerShell のインテリセンスは賢くないのでそんなことしてくれません。当然キャストもしません。

ということは、返戻値の型 と [OutputType()]で示した他型がずれることがふつーにざらにありえます。

例えばこう。

[OutputType()]System.Int32を示したにも関わらず、実際の返戻値は System.Uri型です。

パイプラインで渡してインテリセンスを見てみると?System.Int32の型がインテリセンスで表示されました。実際の型とずれていますね。

f:id:guitarrapc_tech:20150120033224p:plain

nyao | %{$_ | Get-Member}


   TypeName: System.Uri

Name                       MemberType Definition                                                                 
----                       ---------- ----------                                                                 
Equals                     Method     bool Equals(System.Object comparand)                                       
GetComponents              Method     string GetComponents(System.UriComponents components, System.UriFormat f...
GetHashCode                Method     int GetHashCode()                                                          
GetLeftPart                Method     string GetLeftPart(System.UriPartial part)                                 
GetObjectData              Method     void ISerializable.GetObjectData(System.Runtime.Serialization.Serializat...
GetType                    Method     type GetType()                                                             
IsBaseOf                   Method     bool IsBaseOf(uri uri)                                                     
IsWellFormedOriginalString Method     bool IsWellFormedOriginalString()                                          
MakeRelative               Method     string MakeRelative(uri toUri)                                             
MakeRelativeUri            Method     uri MakeRelativeUri(uri uri)                                               
ToString                   Method     string ToString()                                                          
AbsolutePath               Property   string AbsolutePath {get;}                                                 
AbsoluteUri                Property   string AbsoluteUri {get;}                                                  
Authority                  Property   string Authority {get;}                                                    
DnsSafeHost                Property   string DnsSafeHost {get;}                                                  
Fragment                   Property   string Fragment {get;}                                                     
Host                       Property   string Host {get;}                                                         
HostNameType               Property   System.UriHostNameType HostNameType {get;}                                 
IsAbsoluteUri              Property   bool IsAbsoluteUri {get;}                                                  
IsDefaultPort              Property   bool IsDefaultPort {get;}                                                  
IsFile                     Property   bool IsFile {get;}                                                         
IsLoopback                 Property   bool IsLoopback {get;}                                                     
IsUnc                      Property   bool IsUnc {get;}                                                          
LocalPath                  Property   string LocalPath {get;}                                                    
OriginalString             Property   string OriginalString {get;}                                               
PathAndQuery               Property   string PathAndQuery {get;}                                                 
Port                       Property   int Port {get;}                                                            
Query                      Property   string Query {get;}                                                        
Scheme                     Property   string Scheme {get;}                                                       
Segments                   Property   string[] Segments {get;}                                                   
UserEscaped                Property   bool UserEscaped {get;}                                                    
UserInfo                   Property   string UserInfo {get;}                                                     

なんだこれ。厄介としか言いようがありません。警告してくだされ~。

PSCustomObjectの厄介さ

PowerShell を使っていて PSCustomObjectを触ったことがない人は、v2 の人ですね?お疲れ様です。

PSCustomObject は、いろんな意味で PowerShell らしいともいえる、便利だけど特殊な型です。PSCustomObjectを使うと任意のプロパティを含んだオブジェクトがさくっと、低コスト、便利に作れるので多用することが多いでしょう。

しかし、任意のプロパティを自在に含められるため、インテリセンスと相性はさいてーです。サイテー。

たとえば、次のような関数をパイプラインの先で補完してみても?

当然ですが、piyoプロパティはインテリセンスに出ませんね。

f:id:guitarrapc_tech:20150120033906p:plain

まとめ

[OutputType()]大事ですよ?使ってなかった人はぜひ使ってください。

型大事です。実際。もぅ、yield return 用意したりして、[Void]以外はreturn 必須にしたり、[OutputType()] と検査すればいいのにって思っちゃいますね。

*1:必ずparam()ブロックを使って引数を受けないといけません。paramブロックは空でもいいです。

*2:[CmdletBinding]属性は[OutputType]属性とセットにする必要はありません