しばたテックブログ

気分で書いている技術ブログです。

PowerShellにおける"戻り値"と"Return"について

PowerShellの基本的なことがわかってなかったシリーズ第三弾です。
内容としては、牟田口さんのブログの

winscript.jp

で書かれていることそのまんまです。
なので詳しいことを知りたい方はそちらをご覧いただければ十分かと思います。

今回わざわざパクリエントリを書くのは上のエントリを紹介したかったから+自分の備忘録のためになります。

はじめに

以下の超簡単なFunction、Funcを例に説明します。

Function Func(){
    Write-Output "1+1は?"
    return 1 + 1
}

このファンクションを実行するとコンソールには

PS C:\> Func
1+1は?
2

と表示されます。

この時Funcの戻り値は何か?という話しになります。
私はつい最近までこのFuncの戻り値は2だけだと勘違いしていました。

Funcの実行結果を変数$Resultに設定して調べてみると以下の様になります。

PS C:\> $Result = Func

# $Result の型は Object[]
PS C:\> $Result.GetType()

IsPublic IsSerial Name                                     BaseType                                                                 
-------- -------- ----                                     --------                                                                 
True     True     Object[]                                 System.Array                                                             

# 要素数2
PS C:\> $Result.Count
2

# $Result[0]は String型の値 "1+1は?"
PS C:\> $Result[0]
1+1は?

PS C:\> $Result[0].GetType()

IsPublic IsSerial Name                                     BaseType                                                                 
-------- -------- ----                                     --------                                                                 
True     True     String                                   System.Object    

# $Result[1]は Int型の値 2
PS C:\> $Result[1]
2

PS C:\> $Result[1].GetType()

IsPublic IsSerial Name                                     BaseType                                                                 
-------- -------- ----                                     --------                                                                 
True     True     Int32                                    System.ValueType   

実際にFuncから戻される値は最初のWrite-Outputの結果を含んだStringInt32の混ざったObcet[]型の配列となっていることがわかります。

PowerShellの戻り値の評価について

about_Returnにこのへんのことが詳しく記載されています。

In Windows PowerShell, the results of each statement are returned as output, even without a statement that contains the Return keyword.
Languages like C or C# return only the value or values that are specified by the Return keyword.

PowerShellの基本として、戻り値の評価は文(statement)ごとに行われます。

最初の例で言うと、Write-Output "1+1は?"return 1 + 1両方が戻り値の評価対象となります。
これは先の牟田口さんのブログで記載されている様にパイプライン連携のための仕様です。

先ほどのFuncを少し修正したFunc2と簡単なフィルタFilter1を例に動作を確認してみます。

Function Func2(){
    Write-Output "1+1は?"
    Write-Host ("{0:HH:mm:ss.ffffff} | -------- 切り取り線 --------" -F (Get-Date))
    return 1 + 1
}

Filter Filter1(){
    Write-Output ("{0:HH:mm:ss.ffffff} | パイプラインから {1}型 の {2} がきたよ!" -F (Get-Date), $_.GetType().Name, $_ )
}

Func2Filter1にパイプラインで渡してみると、

PS C:\> Func2 | Filter1
20:06:22.488594 | パイプラインから String型 の 1+1は? がきたよ!
20:06:22.497391 | -------- 切り取り線 --------
20:06:22.499652 | パイプラインから Int32型 の 2 がきたよ!

となり、文の評価ごとにパイプラインに結果が渡されていることがわかります。

この仕様については正直どうよ?って思うところも無きにしも非ずですが、個人的な感覚としては、

なんつーか、"式を評価して最終的な値を返す"ってより、"式を評価して随時値をストリームに放流する"って感じがしっくりくる。 (当時のTwitterでのツイートより)

なのかなぁと思いました。
また、.NET系の言語で例えるとPowerShell全体のストリームという巨大なループに対して随時yield returnしているというのがイメージ的に近いかもしれません。

ちなみに値を返したくない場合は式ごとに[void]でキャストしてやる必要があります。
また、値の代入は戻り値無しとして扱われます。

# 代入は戻り値無し扱い。
# ※戻り値がないので当然コンソールには何も表示されない
PS C:\> $Value = Write-Output "1+1は?"

# これは変数を評価するので String型の戻り値が返される
# ※戻り値があるのでコンソールには "1+1は?" と表示される
PS C:\> $Value
1+1は?

# 戻り値を返したくない場合は[void]でキャストする
PS C:\> [void]$Value

補足として、PowerShellの言語仕様書の7章(Expressions)に記載されている例をすこしだけ紹介します。

# 代入なので値は返されない
$a = 1234 * 3.5
# ()でくくると$aが評価されるので 4319 が返される
($a = 1234 * 3.5)

# インクリメントは代入扱い
++$a
# ()でくくると値が返される
(++$a())

# 値は返されない
$($i = 10)
# 10が返される
$(($i = 10))

Returnについて

return文はあくまでも制御を呼び出し元に戻すだけの構文となります。
about_Returnにも書かれていますが、

return $a

$a
return

は等価であり、戻り値に特別な影響を及ぼすことはありません。

【2017/03/20追記】クラス内でのReturnについて

PowerShell 5.0からクラス構文が提供され、PowerShellでクラスの定義ができる様になっています。
クラスではメソッドが定義可能で、メソッド内でreturn文を使用することができます。

クラス内で使用するreturnについてはC#等のreturnと同じ挙動となりますので注意してください。

【改訂新版】 Windows PowerShell ポケットリファレンス

【改訂新版】 Windows PowerShell ポケットリファレンス