値渡しと参照渡しとは、どちらも引数の渡し方の一種です。
各々の特徴は以下の通り
値渡し
対象の変数の値をコピーして引数として渡す方法
呼び出し先で引数の値が変わっても呼び出し元の変数は値が変わりません
class PassValue
{
public PassValue()
{
this.Execute();
}
private int num = 1;
private void Execute() => this.Increment(this.num);
private void Increment(int before)
{
before++;
Console.WriteLine(before); // 出力:2
Console.WriteLine(this.num); // 出力:1(呼び出し元の値は変わらない)
}
}
参照渡し
対象の変数への参照を引数として渡す方法
変数の中身をコピーするわけではなく、変数の中身が置かれている場所を教えてあげるイメージです
こちらは呼び出し先で値が変わると呼び出し元の変数の値も変更されます
C#の実装では引数にキーワードをつけることで3種類の参照渡しが使用可能です
1. ref
通常の参照渡しで、呼び出し元で呼び出し先の値変更が可能。
引数で渡す前に値の初期化がされていることが必須で、メソッドコール時にもref
を記述する必要があります。
2. in
読み取り専用の参照渡しで、"入力参照引数"という。
メソッドコール時はin
を記述しなくてもエラーにはならないが記述することが好ましいです。
inを記述することによって、メソッド内で引数の値が変わらないことを保証します。
一見値渡しと同じ動きに見えますが、値のコピーを必要としないので大きいデータを扱う際はパフォーマンスの向上が見込めます。
3.out
メソッドから変数を渡す"出力引数"という。
メソッドコール時の初期化は不要で、メソッド内で初期化された変数を受け取ります。
Dictionary<TKey, TValue>
のTryGetValue()
等に使用されている手法で、メソッド内で変数を初期化したいケースや複数の返り値を渡したい際に有効です。
実装例
class PassReference
{
public PassReference()
{
// ref (引数で渡した値も変更される)
int before = 1;
this.UseRef(ref before);
Console.WriteLine(before); // 出力:2
// out (複数の返り値を受け取ることができる)
before = 1;
if (this.UseOut(before, out int outNum))
{
Console.WriteLine(outNum); // 出力:2
}
else
{
Console.WriteLine("数値が範囲外です");
}
}
// refを使ったメソッド
private void UseRef(ref int before)
{
before++;
}
//inを使ったメソッド
private int UseIn(in int before)
{
return before++; // 値変更不可のためエラーになる
}
// outを使ったメソッド
private bool UseOut(int before, out int incremented)
{
// インクリメント後の値の範囲チェックとインクリメント後の値を同時にreturn
incremented = before + 1;
return incremented > 0;
}
}
参考