【C#】引数のref/outと参照型の関係のまとめ(constの代わりはinキーワードで)
もしかしたら今更なのかもしれない。なんとなくC#を勉強中。
メソッドの引数についての仕様は分かったのだが、疑問が生まれる。
とりあえずの覚え書き。
認識のおかしいところがありましたらご指摘お願いします。
引数のキーワードには、refとoutが指示できる。
【ref】
ref キーワードをつけると、引数の値ではなく参照が渡されます。 参照渡しで渡すことにより、呼び出されたメソッドのパラメーターに対する変更が、呼び出し元のメソッドに反映されます。 たとえば、呼び出し元がローカル変数の式、または配列要素のアクセス式を渡し、呼び出されたメソッドが ref パラメーターが参照するオブジェクトを置き換える場合、呼び出し元のローカル変数または配列要素は新しいオブジェクトを参照します。 - MSDNより
【out】
out キーワードによって、参照により引数が渡されます。 これは、ref キーワードと同様ですが、ref は渡す前に、変数を初期化する必要があります。 - MSDNより
どちらも渡した変数の値を書き換えることができるが、要はrefはメソッドに渡す前に値を設定しておく、outはメソッド内で必ず値を設定する(呼び出し元で設定はしなくてもよい)と言うことか。
つまり、refは[入出力引数]で、outは[出力引数]で使うイメージすればいいかな。
さてこの辺は良いとして、C#の変数には値型と参照型がある。
値型は変数に値がそのまま入っている。
参照型は変数に割り当てた値の参照(値の場所を示す位置)が入っている。
なので、値型はキーワードなしで引数に渡しても、値が代入されて渡されるだけ。
方や参照型はキーワードなしで引数に渡すと、参照を代入して渡すので、その示す位置はメッソド内も呼び出し元も同じ。つまりメソッド内で変更すると呼び出し元の変数にも影響する。
そう、その名の通り参照型と言っているので、キーワードなしでも、値型にrefをつけた時と同じような動きとなる。
じゃあ、参照型にrefをつけたら?
同MSDNより。
参照型を参照渡しで渡すと、呼び出されたメソッドで、参照パラメーターが参照する呼び出し元のメソッドのオブジェクトを置換できます。
そらそうだ、引数が“参照”になっているので、その中身である参照(値の場所を示す位置)を変える事ができると言うことか。
outについても同じ。
ただ、上で書いたrefは[入出力引数]と言う事で考えると、refが付けられた参照型はメソッド内で入力値としても見るし参照先も変更する(かもしれない)。
これ、変更されるかもしれない、なんて呼び出し元からしたら非常に危なっかしい気がする。
outならメソッド内で設定するのが仕様なので必ず設定されることが分かるから、使うとすればoutでref参照型の出番なんてなさそう。
「てか、これってC/C++で言うポインタの扱いと一緒では」
C#はC/C++で悪名高いポインタが無くなったと思っていたが、参照型って結局ポインタみたいで、そうなると参照型の引数はC/C++で言うポインタ引数(もしくは参照渡し)、ref/outがつくとダブルポインタってイメージか?
たしかに、MSDNのここ(「2-3 値型と参照型」)で「C# の参照型は C++ のポインタ型といえます。」 と書いてる。
そう考えると同じように結局デリケートに扱わないといけないのでは。。
さてここで、
疑問その1 参照型を書き換え不可にできないの?
参照型がポインタみたい、とするとC/C++で入力引数だがポインタを引数で渡したい場合は、const 変数名*とかC++ならconst 変数名&とか書いて、メソッド内で書き換えないことを明示的に示す。
(ただ、constを外そうと思えば外せちゃうけど)
じゃあC#では?と思ったら、、 無い。
C#にもconstはあるし、readonlyと言う修飾もあるが引数に書けない。。
同じような疑問を持つ方も。
結論としてはいらない機能を落としたから?となっているけど、本当にいらない機能なのか…?
この辺、Javaのfinalについても同じような議論がありますね。
変更しない変数にfinalを付けるべきですか? − Java Solution − @IT
C#でJavaのfinalの1機能を使いたい − Java Solution − @IT
(追記)
そう思って記事を書いて数年が立ちましたが、ついにC# 7.2でこのconst相当に当たる「in」と言うキーワードが追加されました。これで引数をconst 引数&相当の読み取り専用の参照にすることができます。これでstructの存在価値が上がりそうです。
ただし、残念ながら参照型についてはメソッド内での割り当てができないだけでJavaのfinalと同じようにメンバにはアクセスできてしまいます。
疑問その2 そもそもrefやoutって使うの?
これは、.Net Frameworkのライブラリを眺めていると、refやoutがついているメソッドが見当たらない。
見つけられたのは、型変換を行うTryParseのメソッドくらい。
またこれをTryParseパターンと言うらしく、戻り値でステータスを返し結果をout引数にするパターン。
大抵は、戻り値で結果を返し、正常以外のステータスは例外を飛ばすようにするのが普通?のようで、例外を出さないように呼び出し元でチェックしろ、必要ならチェック関数を提供する、と言うことらしい。
それならそうとして、出力結果を2つ以上返したい時はどうするのだろう。
そもそも、そんな設計にするな。
返したいのであれば構造体やクラスなどを定義してその型で返すべき、と言うことなのだろうか。
確かにここでもその様に書いていますね。
また、そのためにTuple クラスなんてのが用意されてる。
※ただし、このクラスはメンバが全てItem1,Item2…と表示に雑になってしまうので、作業用的な狭い範囲での仕様に限る方が良さそう。
※名前付きの定義ができたらなぁと思っていたら、C# 7からValueTuple 構造体 なんてのが使えるらしい。Tupleと違って構造体であることが留意点か。
こちらについてはそう言うものかと一先ず納得。
スポンサード リンク