PowerShell から .NET のメソッドに引数を参照渡しするときに、何回調べても [ref]
をつける箇所が分からなくなるので、今年の自由研究のテーマにしました。虫捕りと迷ったのですが、こちらにします。
.NET の [DateTime]::TryParseExact
メソッドで検証します。
前提
作業環境は以下の通りです。
- Windows PowerShell 5.1.19041.1151
- Windows 10 20H2
今回は .NET のメソッドを直接呼び出す方法と、function や scriptblock にして呼び出す方法を考えます。[ref]
の部分に焦点を当てるため、その他の部分では変数を使いません。
変数 $parsedDateTime
に [DateTime]::MinValue
を代入し、[ref]
を付けたり付けなかったりしながら [DateTime]::TryParseExact(String, String, IFormatProvider, DateTimeStyles, DateTime)
に渡します。
[DateTime]::TryParseExact(String, String, IFormatProvider, DateTimeStyles, DateTime)(英語)
[DateTime]::TryParseExact(String, String, IFormatProvider, DateTimeStyles, DateTime)(日本語)
検証したいこと
- どこに
[ref]
を付けたらエラーが出ないか [DateTime]::TryParseExact
の引数 Result に.Value
が必要かどうか- function 等の仮引数に
[DateTime]
を指定していいかどうか
[ref]
の位置をはっきりさせることが今回の主目的です。
それから、function に参照渡しした変数値にアクセスするには、こんな感じで .Value
プロパティを使うとの情報が見つかるのですが、
Function Test([ref]$data) { $data.Value = 3 }
これに従うとうまく行かないことがあったので合わせて検証します。
ついでに、以下のように function 等の仮引数に [DateTime]
を指定していいかどうかも確認します。
function TestByRef { param ( [DateTime] Result # 仮引数 ) ... }
検証するパターン
.NET
.NET のメソッドを直接呼び出すなら、まずこの2か所に [ref]
が付く可能性があります。
[ref]$parsedDateTime = [DateTime]::MinValue # 変数 [DateTime]::TryParseExact( '2021-08-11 12:34:56', 'yyyy-MM-dd HH-mm-ss', [System.Globalization.DateTimeFormatInfo]::InvariantInfo, [System.Globalization.DateTimeStyles]::None, [ref]$parsedDateTime # .NET の引数 )
- 変数
- .NET の引数
以上の2か所に [ref]
を付けるかどうかと、.Value
を付けるかどうかということで、次の8パターンを検証すればいいことになります。
パターン | 変数 | .NET | .Value |
---|---|---|---|
1 | - | - | - |
2 | - | - | .Value |
3 | - | [ref] | - |
4 | - | [ref] | .Value |
5 | [ref] | - | - |
6 | [ref] | - | .Value |
7 | [ref] | [ref] | - |
8 | [ref] | [ref] | .Value |
function/scriptblock
function と scriptblock なら、この4か所に [ref]
が付く可能性があります。
[ref]$parsedDateTime = [DateTime]::MinValue # 変数 function TestByRef { param ( [ref] Result # 仮引数 ) [DateTime]::TryParseExact( '2021-08-11 12:34:56', 'yyyy-MM-dd HH-mm-ss', [System.Globalization.DateTimeFormatInfo]::InvariantInfo, [System.Globalization.DateTimeStyles]::None, [ref]$parsedDateTime # .NET の引数 ) } TestByRef ([ref]$parsedDateTime) # 実引数
[ref]$parsedDateTime = [DateTime]::MinValue # 変数 [scriptblock]$testByRef = { param ( [ref] Result # 仮引数 ) [DateTime]::TryParseExact( '2021-08-11 12:34:56', 'yyyy-MM-dd HH-mm-ss', [System.Globalization.DateTimeFormatInfo]::InvariantInfo, [System.Globalization.DateTimeStyles]::None, [ref]$parsedDateTime # .NET の引数 ) } $testByRef.Invoke(([ref]$parsedDateTime)) # 実引数
- 変数
- .NET の引数
- 仮引数(param 節)
- 実引数(function/scriptblock に渡す引数)
以上の4か所の [ref]
と .Value
の有無、それと仮引数を [DateTime]
型にすることも考えて、次の64パターンを検証します。仮引数に [ref]
と [DateTime]
を両方付けるパターンも試します。以前やったことがあるので。
多いな……やっぱり虫捕りの方がよかったかなぁ……。
パターン | 変数 | .NET | .Value | 仮引数 [ref] | 仮引数 [DateTime] | 実引数 |
---|---|---|---|---|---|---|
9 | - | - | - | - | - | - |
10 | - | - | - | - | - | [ref] |
11 | - | - | - | - | [DateTime] | - |
12 | - | - | - | - | [DateTime] | [ref] |
13 | - | - | - | [ref] | - | - |
14 | - | - | - | [ref] | - | [ref] |
15 | - | - | - | [ref] | [DateTime] | - |
16 | - | - | - | [ref] | [DateTime] | [ref] |
17 | - | - | .Value | - | - | - |
18 | - | - | .Value | - | - | [ref] |
19 | - | - | .Value | - | [DateTime] | - |
20 | - | - | .Value | - | [DateTime] | [ref] |
21 | - | - | .Value | [ref] | - | - |
22 | - | - | .Value | [ref] | - | [ref] |
23 | - | - | .Value | [ref] | [DateTime] | - |
24 | - | - | .Value | [ref] | [DateTime] | [ref] |
25 | - | [ref] | - | - | - | - |
26 | - | [ref] | - | - | - | [ref] |
27 | - | [ref] | - | - | [DateTime] | - |
28 | - | [ref] | - | - | [DateTime] | [ref] |
29 | - | [ref] | - | [ref] | - | - |
30 | - | [ref] | - | [ref] | - | [ref] |
31 | - | [ref] | - | [ref] | [DateTime] | - |
32 | - | [ref] | - | [ref] | [DateTime] | [ref] |
33 | - | [ref] | .Value | - | - | - |
34 | - | [ref] | .Value | - | - | [ref] |
35 | - | [ref] | .Value | - | [DateTime] | - |
36 | - | [ref] | .Value | - | [DateTime] | [ref] |
37 | - | [ref] | .Value | [ref] | - | - |
38 | - | [ref] | .Value | [ref] | - | [ref] |
39 | - | [ref] | .Value | [ref] | [DateTime] | - |
40 | - | [ref] | .Value | [ref] | [DateTime] | [ref] |
41 | [ref] | - | - | - | - | - |
42 | [ref] | - | - | - | - | [ref] |
43 | [ref] | - | - | - | [DateTime] | - |
44 | [ref] | - | - | - | [DateTime] | [ref] |
45 | [ref] | - | - | [ref] | - | - |
46 | [ref] | - | - | [ref] | - | [ref] |
47 | [ref] | - | - | [ref] | [DateTime] | - |
48 | [ref] | - | - | [ref] | [DateTime] | [ref] |
49 | [ref] | - | .Value | - | - | - |
50 | [ref] | - | .Value | - | - | [ref] |
51 | [ref] | - | .Value | - | [DateTime] | - |
52 | [ref] | - | .Value | - | [DateTime] | [ref] |
53 | [ref] | - | .Value | [ref] | - | - |
54 | [ref] | - | .Value | [ref] | - | [ref] |
55 | [ref] | - | .Value | [ref] | [DateTime] | - |
56 | [ref] | - | .Value | [ref] | [DateTime] | [ref] |
57 | [ref] | [ref] | - | - | - | - |
58 | [ref] | [ref] | - | - | - | [ref] |
59 | [ref] | [ref] | - | - | [DateTime] | - |
60 | [ref] | [ref] | - | - | [DateTime] | [ref] |
61 | [ref] | [ref] | - | [ref] | - | - |
62 | [ref] | [ref] | - | [ref] | - | [ref] |
63 | [ref] | [ref] | - | [ref] | [DateTime] | - |
64 | [ref] | [ref] | - | [ref] | [DateTime] | [ref] |
65 | [ref] | [ref] | .Value | - | - | - |
66 | [ref] | [ref] | .Value | - | - | [ref] |
67 | [ref] | [ref] | .Value | - | [DateTime] | - |
68 | [ref] | [ref] | .Value | - | [DateTime] | [ref] |
69 | [ref] | [ref] | .Value | [ref] | - | - |
70 | [ref] | [ref] | .Value | [ref] | - | [ref] |
71 | [ref] | [ref] | .Value | [ref] | [DateTime] | - |
72 | [ref] | [ref] | .Value | [ref] | [DateTime] | [ref] |
結果
.NET
パターン | 変数 | .NET | .Value | 結果 |
---|---|---|---|---|
1 | - | - | - | 失敗 |
2 | - | - | .Value | 失敗 |
3 | - | [ref] | - | 成功 |
4 | - | [ref] | .Value | 失敗 |
5 | [ref] | - | - | ※1 |
6 | [ref] | - | .Value | 失敗 |
7 | [ref] | [ref] | - | 失敗 |
8 | [ref] | [ref] | .Value | ※1※2 |
※1 エラーは出ないが $parsedDateTime
の型が [PSReference]
型のままになっている
※2 エラーは出ないが $parsedDateTime
の中身が初期値のままになっている
パターン 1 のエラーメッセージに従えば解決です。
引数 '5' は System.Management.Automation.PSReference でなければなりません。[ref] を使用してください。
パターン 5 は成功しますが、変数 $parsedDateTime
が [PSReference]
型のままですので、[DateTime]
型としては扱えません。型変換も不可能なようです。たぶん。
というわけでパターン 3 一択です。
$parsedDateTime = [DateTime]::MinValue [DateTime]::TryParseExact( '2021-08-11 12-34-56', 'yyyy-MM-dd HH-mm-ss', [System.Globalization.DateTimeFormatInfo]::InvariantInfo, [System.Globalization.DateTimeStyles]::None, [ref]$parsedDateTime ) # True $parsedDateTime # 2021年8月11日 12:34:56
function/scriptblock
検証用のスクリプトを function 用と scriptblock 用にそれぞれ64パターン作るスクリプトを書いて、その 64 * 2 = 128 のスクリプトをスクリプトで実行した結果をスクリプトでまとめました(?)
こちらに御座います。
taidalog/PowerShell_Ref - GitHub
パターン | 変数 | .NET | .Value | 仮引数 [ref] | 仮引数 [DateTime] | 実引数 | function の結果 | scriptblock の結果 |
---|---|---|---|---|---|---|---|---|
9 | - | - | - | - | - | - | 失敗 | 失敗 |
10 | - | - | - | - | - | [ref] | 成功 | 成功 |
11 | - | - | - | - | [DateTime] | - | 失敗 | 失敗 |
12 | - | - | - | - | [DateTime] | [ref] | 失敗 | 失敗 |
13 | - | - | - | [ref] | - | - | 失敗 | ※2 |
14 | - | - | - | [ref] | - | [ref] | 成功 | 成功 |
15 | - | - | - | [ref] | [DateTime] | - | 失敗 | ※2 |
16 | - | - | - | [ref] | [DateTime] | [ref] | 成功 | 失敗 |
17 | - | - | .Value | - | - | - | 失敗 | 失敗 |
18 | - | - | .Value | - | - | [ref] | 失敗 | 失敗 |
19 | - | - | .Value | - | [DateTime] | - | 失敗 | 失敗 |
20 | - | - | .Value | - | [DateTime] | [ref] | 失敗 | 失敗 |
21 | - | - | .Value | [ref] | - | - | 失敗 | 失敗 |
22 | - | - | .Value | [ref] | - | [ref] | 失敗 | 失敗 |
23 | - | - | .Value | [ref] | [DateTime] | - | 失敗 | 失敗 |
24 | - | - | .Value | [ref] | [DateTime] | [ref] | 失敗 | 失敗 |
25 | - | [ref] | - | - | - | - | ※2 | ※2 |
26 | - | [ref] | - | - | - | [ref] | 失敗 | 失敗 |
27 | - | [ref] | - | - | [DateTime] | - | ※2 | ※2 |
28 | - | [ref] | - | - | [DateTime] | [ref] | ※2 | 失敗 |
29 | - | [ref] | - | [ref] | - | - | 失敗 | 失敗 |
30 | - | [ref] | - | [ref] | - | [ref] | 失敗 | 失敗 |
31 | - | [ref] | - | [ref] | [DateTime] | - | 失敗 | 失敗 |
32 | - | [ref] | - | [ref] | [DateTime] | [ref] | 失敗 | 失敗 |
33 | - | [ref] | .Value | - | - | - | 失敗 | 失敗 |
34 | - | [ref] | .Value | - | - | [ref] | ※2 | ※2 |
35 | - | [ref] | .Value | - | [DateTime] | - | 失敗 | 失敗 |
36 | - | [ref] | .Value | - | [DateTime] | [ref] | 失敗 | 失敗 |
37 | - | [ref] | .Value | [ref] | - | - | 失敗 | ※2 |
38 | - | [ref] | .Value | [ref] | - | [ref] | ※2 | ※2 |
39 | - | [ref] | .Value | [ref] | [DateTime] | - | 失敗 | ※2 |
40 | - | [ref] | .Value | [ref] | [DateTime] | [ref] | ※2 | 失敗 |
41 | [ref] | - | - | - | - | - | ※1 | ※1 |
42 | [ref] | - | - | - | - | [ref] | 失敗 | 失敗 |
43 | [ref] | - | - | - | [DateTime] | - | 失敗 | 失敗 |
44 | [ref] | - | - | - | [DateTime] | [ref] | 失敗 | 失敗 |
45 | [ref] | - | - | [ref] | - | - | ※1 | ※1 |
46 | [ref] | - | - | [ref] | - | [ref] | 失敗 | 失敗 |
47 | [ref] | - | - | [ref] | [DateTime] | - | ※1 | 失敗 |
48 | [ref] | - | - | [ref] | [DateTime] | [ref] | 失敗 | 失敗 |
49 | [ref] | - | .Value | - | - | - | 失敗 | 失敗 |
50 | [ref] | - | .Value | - | - | [ref] | ※1 | ※1 |
51 | [ref] | - | .Value | - | [DateTime] | - | 失敗 | 失敗 |
52 | [ref] | - | .Value | - | [DateTime] | [ref] | 失敗 | 失敗 |
53 | [ref] | - | .Value | [ref] | - | - | 失敗 | 失敗 |
54 | [ref] | - | .Value | [ref] | - | [ref] | ※1 | ※1 |
55 | [ref] | - | .Value | [ref] | [DateTime] | - | 失敗 | 失敗 |
56 | [ref] | - | .Value | [ref] | [DateTime] | [ref] | ※1 | 失敗 |
57 | [ref] | [ref] | - | - | - | - | 失敗 | 失敗 |
58 | [ref] | [ref] | - | - | - | [ref] | 失敗 | 失敗 |
59 | [ref] | [ref] | - | - | [DateTime] | - | ※1※2 | 失敗 |
60 | [ref] | [ref] | - | - | [DateTime] | [ref] | 失敗 | 失敗 |
61 | [ref] | [ref] | - | [ref] | - | - | 失敗 | 失敗 |
62 | [ref] | [ref] | - | [ref] | - | [ref] | 失敗 | 失敗 |
63 | [ref] | [ref] | - | [ref] | [DateTime] | - | 失敗 | 失敗 |
64 | [ref] | [ref] | - | [ref] | [DateTime] | [ref] | 失敗 | 失敗 |
65 | [ref] | [ref] | .Value | - | - | - | ※1※2 | ※1※2 |
66 | [ref] | [ref] | .Value | - | - | [ref] | ※1 | ※1 |
67 | [ref] | [ref] | .Value | - | [DateTime] | - | 失敗 | 失敗 |
68 | [ref] | [ref] | .Value | - | [DateTime] | [ref] | 失敗 | 失敗 |
69 | [ref] | [ref] | .Value | [ref] | - | - | ※1※2 | ※1※2 |
70 | [ref] | [ref] | .Value | [ref] | - | [ref] | ※1 | ※1 |
71 | [ref] | [ref] | .Value | [ref] | [DateTime] | - | ※1※2 | 失敗 |
72 | [ref] | [ref] | .Value | [ref] | [DateTime] | [ref] | ※1 | 失敗 |
※1 エラーは出ないが $parsedDateTime
の型が [PSReference]
型のままになっている
※2 エラーは出ないが $parsedDateTime
の中身が初期値のままになっている
function と scriptblock で結果が変わる箇所があるのが意外でした。プログラムの仕組みをもっと勉強すればこういうのも理解できるんだろうな。
.NET の引数に [ref]
を付けるとことごとく失敗するというのは大きな発見でした。真っ先に [ref]
を付けたくなる箇所なのですけどね。
仮引数に [ref]
と [DateTime]
を両方付けるというカオスが許されるものなんですね。他の型が渡ってしまうのは [ValidateScript]
でなんとかなるかな。
.Value
を付けても失敗しました。なんでや。
まとめると、以下の3点に気を付ければいいようです。
- function の仮引数に
[DateTime]
を単独でつけてはいけない - function の実引数には
[ref]
を付ける - それ以外は何も付けない
ということでパターン 10 でいいと思います。
function TestByRef { param ( $Result ) [DateTime]::TryParseExact( '2021-08-11 12-34-56', 'yyyy-MM-dd HH-mm-ss', [System.Globalization.DateTimeFormatInfo]::InvariantInfo, [System.Globalization.DateTimeStyles]::None, $Result ) } $parsedDateTime = [DateTime]::MinValue TestByRef ([ref]$parsedDateTime) # True $parsedDateTime # 2021年8月11日 12:34:56
[scriptblock]$testByRef = { param ( $Result ) [DateTime]::TryParseExact( '2021-08-11 12-34-56', 'yyyy-MM-dd HH-mm-ss', [System.Globalization.DateTimeFormatInfo]::InvariantInfo, [System.Globalization.DateTimeStyles]::None, $Result ) } $parsedDateTime = [DateTime]::MinValue $testByRef.Invoke(([ref]$parsedDateTime)) # True $parsedDateTime # 2021年8月11日 12:34:56
他のメソッドでも確認
他の .NET メソッドでも試しましょう。[Int32]::TryParse(String, Int32)
メソッドを使います。
[Int32]::TryParse(String, Int32)(英語)
[Int32]::TryParse(String, Int32)(日本語)
$parsedInt = [Int]::MinValue [Int]::TryParse('1', [ref]$parsedInt) # True $parsedInt # 1
function TestIntTryParse { param ( $Result ) [int]::TryParse('1', $Result) } $parsedInt = [Int]::MinValue TestIntTryParse ([ref]$parsedInt) # True $parsedInt # 1
[scriptblock]$testIntTryParse = { param ( $Result ) [Int]::TryParse('1', $Result) } $parsedInt = [Int]::MinValue $testIntTryParse.Invoke(([ref]$parsedInt)) # True $parsedInt # 1
よし、全部クリア!
function への参照渡しについてわかったこと
カッコ必須です。
TestByRef ([ref]$parsedDateTime) # OK TestByRef [ref]$parsedDateTime # NG
値渡しと混在させてもOKです。
function TestByRef { param ( [string] $s, $Result ) [DateTime]::TryParseExact( $s, 'yyyy-MM-dd HH-mm-ss', [System.Globalization.DateTimeFormatInfo]::InvariantInfo, [System.Globalization.DateTimeStyles]::None, $Result ) } TestByRef '2021-08-11 12-34-56' ([ref]$parsedDateTime) # OK
名前付き引数で渡す例をあまり見ませんが、問題なくできます。
TestByRef -s '2021-08-11 12-34-56' -Result ([ref]$parsedDateTime) # OK
スプラッティングもできます。
$params = @{ s = '2021-08-11 12-34-56' Result = ([ref]$parsedDateTime) } TestByRef @params # OK
パイプラインから渡す方法は上手くいきませんでした。
$parsedDateTime | %{TestByRef -s '2021-08-11 12-34-56' -Result ([ref]$_) # エラーは出ないが変換できない
感想
思いついてから完成まで3日くらいかかりました。疲れたぜ。
検証するにあたってたくさんデバッグしましたので、これもひとつの「虫取り」ということで。
参考
about Ref - PowerShell | Microsoft Docs
ref について - PowerShell | Microsoft Docs