はかせのラボ

私の頭の中を書いていく雑記ブログです

C# 参照渡しの3つのキーワードについて違いを語る ~in、out、ref~

あいさつ

どうも、はかせです。

先日メソッドから複数の値を取得する方法を
投稿しました。
hakase0274.hatenablog.com

その後Twitterの方でですね
「outは?」
というコメントを頂きました。

確かに複数の値を返すという名目ならば
refよりoutの方がより意味合いとして近い気がします。

ただお恥ずかしながら私自身
refとoutの違いを言語化できるほど理解していませんでした。

なので今回は私の勉強メモという側面を持ちつつ、
C#の参照渡しで指定できる3つのキーワードそれぞれについて
書いていきたいと思います。

参照渡しと値渡し

それぞれの違いを語る前にC#に存在している
参照渡しと値渡しについて書きます。
(これないと話わからなくなるとこあるんで)

どちらもメソッドにデータを渡す方法ではあるのですが、
渡し方が少し異なります。

まずおそらくみなさん無意識的にでも使っている値渡しからです。
これは素直にメソッド定義したら自動的にこうなります。

値渡しでは引数に入れた
データのコピーがメソッドに渡ります

void Main()
{
    int hoge = 10;
    HogeHoge(hoge);
}
//値渡し hogeのコピーが渡る
void HogeHoge(int hoge){}

渡っているのが本体ではなくコピーなので
渡したメソッド内でいくら弄ろうと呼び出し元に
影響を及ぼさないわけですね。

対して参照渡しでは引数に入れた
データの参照がメソッドに渡ります

void Main()
{
    int hoge = 10;
    HogeHoge(ref hoge);
}
//参照渡し hogeの参照が渡る
void HogeHoge(ref int hoge){}

要は
「ここにデータあるからこれ使ってね」
的な情報が渡るわけです。

そしてメソッド内ではそのデータを扱うので
弄った結果がそのまま呼び出し元、参照元に反映されるわけです。

ref

参照渡し、値渡しについて軽く書いたところで
いよいよ本題の参照渡しのキーワードです。

まずは先日の記事内でも使ったrefキーワード。

これを指定すると
「読み書き可能な参照」を渡します。
なので渡された参照を読み取ることも書き換えることもメソッドはできます。

読み書き可能な参照を渡す関係上渡すデータは
必ず呼び出し元で初期化されている必要があります

void Main()
{
    //渡す前に初期化しておく
    int hoge = 10;
    HogeHoge(ref hoge);
}
//参照渡し hogeの参照が渡る hogeの中身は10
void HogeHoge(ref int hoge){}

参照渡ししたいけど何使えばいいかようわからん
ってときはこれ使っておけばとりあえず大丈夫な気がします。

out

Twitter上で言及のあったoutキーワードです。

これを指定すると
「書き込み可能な参照」を渡します。

refでは参照渡しするデータは初期化されている必要がありましたが、
outではその必要はありません
その代わりoutを指定したメソッド内では
渡されたout引数に値を代入する義務が発生します

void Main()
{
    //渡す前に初期化してなくてもOK
    int hoge;
    HogeHoge(out hoge);
}
//参照渡し hogeの参照が渡る hogeの中身は10
void HogeHoge(out int hoge)
{
    //必ず値を入れる必要がある 入れなきゃエラー
    hoge = 10
}

代入しなきゃ赤エラー出してくれるので
代入忘れが防止されます。

またoutは式中で宣言することもできます。

void Main()
{
    //式中で変数宣言してもOK
    HogeHoge(out int hoge);
    //もちろんスコープ内であれば変数は生きている
    Console.WriteLine(hoge);
    //ちなみにvarも行けるらしいよ
    HogeHoge(out var fuga);
}
//参照渡し hogeの参照が渡る
void HogeHoge(out int hoge)
{
    //必ず値を入れる必要がある 入れなきゃエラー
    hoge = 10
}

戻り値として参照渡しを使うという場面であれば
エラー出してくれるし、式中宣言もできるしで
こちらの方が適していますね。
(私の不勉強がバレた・・・)

in

初登場inキーワードです。

これを指定すると
「読み取り専用の参照」を渡します。
readonlyの参照渡し版的な感じですね。

読み取りなのでrefと同じく渡すデータは初期化必須です
また専用なので書き込み等はできません
やろうとしたらエラーになります。

void Main()
{
    //渡す前に初期化しておく
    int hoge = 10;
    HogeHoge(in hoge);
}
//参照渡し hogeの参照が渡る hogeの中身は10
void HogeHoge(in int hoge)
{
    //代入ダメ絶対
    hoge = 20;
}

inが有用な場面は何かと言ったら
でかい構造体の受け渡しです。

intだのdouble程度のサイズの
データなら別にコピー取られても
大して問題ないですが、
それこそ行列を管理するような構造体の場合
コピーコストが結構馬鹿になりません

そういうでかい構造体をやり取りするときに
inキーワードは効力を発揮するそうですよ。
(実は使ったことない)

あとがき

今回は参照渡しの3つのキーワードについてでした。

書いててファイルI/Oとかの
権限の仕組みに似ているなって思いました。
あれも読み書きの権限管理しますし。

あと今回仰々しく3つのキーワードなんて言いましたけど
なにやら.NET的には全部refなんだそうです。

あくまでこの制限はコンパイラーレベルでの制限ってことですね。
まぁIL書きでもしない限りコンパイラーレベルで制限しておけば
そこまで不便もないでしょうしね。
(ライブラリ作る人とかにはあるらしいですけど)

今回の記事が良ければスターやコメント等よろしくお願いします。
それでは今回はこの辺でノシ