C#のメソッド呼び出しをする際に引数を渡してメソッド内で引数の値を使う事は多々あります。
メソッド呼び出し時の引数の渡し方には「値渡し」と「参照渡し」の二通りの方法があるので、今回はそれについて勉強していきます。
後述する引数に付けるキーワードのrefとoutを使う頻度は少ないかも?しれません。
Unityの場合はPhysics.Raycastの引数でoutが出てきたりするので意味を理解しておくと何をしているのか?がわかります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class RaycastSample : MonoBehaviour { private void Update() { RaycastHit hit; if (Physics.Raycast(transform.position, transform.forward, out hit, 10f)) { Debug.Log(hit.transform.gameObject.name + " : " + hit.point); } } } |
変数の種類
メソッドに渡す引数の渡し方の前に、変数の種類について少し見ていきます。
変数については、
を見てください。
変数には値型と参照型があり、値型は値そのもの、参照型は値がある場所を指し示す参照値が入っています。
値型の変数を引数で渡す
値型と参照型の変数の違いがわかったところでメソッド呼び出し時に値型の変数を渡して変数の値がどうなるのかを見てみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class PassByValue : MonoBehaviour { // Start is called before the first frame update void Start() { // int型の変数intValue int intValue = 1; // 値型であるintValueを引数として Debug.Log(Increase(intValue)); // intValueの値を表示 Debug.Log(intValue); } int Increase(int intValue) { intValue += 1; return intValue; } } |
値型であるint型の変数intValueに1を入れた後、IncreaseメソッドにintValueを引数として渡して呼び出します。
IncreaseメソッドはintValueに引数として渡ってきた値を入れ、それに1を足してその値を呼び出し元に返しています。
Unityのコンソールには結果として
2
1
が表示されます。
Increaseの呼び出しから得られた戻り値は2で、Increase呼出し後にintValueの値を表示すると1が表示されていることになります。
この場合は値型の変数をそのまま引数で指定している為、引数は値渡し(変数の値のコピー)をしていて、Increase呼出しの後StartメソッドでintValueの値を表示してもintValueの値は変わっていません。
StartとIncreaseで同じ名前のintValueを使っていますが、これはまったく別物です。
参照型の変数を引数に渡す
次に参照型の変数をメソッド呼び出し時に引数として渡してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class PassByReference : MonoBehaviour { // Start is called before the first frame update void Start() { // 参照型であるint型の配列 int[] intArray = new int[] { 0, 1, 2, 3 }; // 配列の要素を表示 foreach (var item in intArray) { Debug.Log("初期配列要素: " + item); } // 引数に参照型変数を渡し呼び出す ChangeArrayValue(intArray); // 確認の為、配列の要素を表示 foreach (var item in intArray) { Debug.Log("メソッド呼び出し後配列要素: " + item); } } // 受け取った配列の0番目の要素を書き換え void ChangeArrayValue(int[] tempIntArray) { tempIntArray[0] = 5; } } |
参照型であるint型の配列を宣言し、最初に要素を表示しています。
その後ChangeArrayValueメソッドにintArrayを引数として渡し、その後intArrayの要素を再度表示しています。
ChangeArrayValueの引数には参照型であるintArrayを渡しているので、引数としては値の場所を示す参照値が渡っています。
ChangeArrayValueメソッドではtempIntArrayにintArrayの参照値を受け取り、配列の0番目の要素を5に書き換えます。
その為StartメソッドでChangeArrayValueを呼び出した後にintArrayの要素を表示すると
5
1
2
3
と表示されます。
値型と違って参照型の場合は値がある場所を指し示す参照値が渡っている為、ChangeArrayValueメソッドで要素の書き換えが起こるとStartメソッド内のintArrayの値も書き換わってしまいます。
refを使った参照渡し
値型と参照型の変数を引数に渡す違いが見れましたが、refを使うと明示的に参照渡しをすることが出来ます。
例えば値型の変数を引数として渡す時に渡す引数の前にrefを書きます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class RefTest : MonoBehaviour { // Start is called before the first frame update void Start() { int intValue = 1; // refを付けて引数を渡す Debug.Log(Increase(ref intValue)); // StartメソッドのintValueの表示 Debug.Log(intValue); } int Increase(ref int intValue) { intValue++; return intValue; } } |
RefTestは最初に示したPassByValueスクリプトのメソッド呼び出し時の実引数とメソッドの仮引数にrefを付けただけです。
結果として表示されるのは
2
2
となります。
refを付けてintValueを渡しているのでintValueの参照値がIncreaseに渡されその値を書き換えているのでStartメソッド内のintValueも書き換えられます。
上の例ではIncreaseで戻り値を返していますが参照で渡ってきた値を書き換えている為、特に値を返す必要がありません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class RefTest2 : MonoBehaviour { // Start is called before the first frame update void Start() { int intValue = 1; // refを付けて引数を渡す Increase(ref intValue); // StartメソッドのintValueの表示 Debug.Log(intValue); } void Increase(ref int intValue) { intValue++; } } |
outを使った参照渡し
refと似たようなものにoutを使った参照渡しがあります。
outを使った引数は参照渡しになり、呼び出し先のメソッドで初期化をする必要があります。
先ほど作成したRefTest2スクリプトをrefからoutに変更してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class OutTest1 : MonoBehaviour { // Start is called before the first frame update void Start() { int intValue; // refを付けて引数を渡す Increase(out intValue); // StartメソッドのintValueの表示 Debug.Log(intValue); } void Increase(out int intValue) { intValue = 10; intValue++; } } |
RefTest2のスクリプトのrefをoutにし、Startメソッド内のintValueの初期化をなくし、Increaseメソッド内でintValueの初期化をしています。
Increaseの呼び出し時に渡すintValueにoutが付いているのでStartメソッド内でのintValueの初期化をしません。
IncreaseメソッドのintValueにもoutを付け、intValueの初期化をしその値をインクリメントしています。
Increaseメソッド呼び出し時の引数intValueにはoutキーワードが付いているので参照渡しとなりIncreaseで戻り値を返さなくてもStartメソッド内のintValueの値が変更されます。
上のスクリプトでは11がコンソールに表示されます。
OutTest1では値型の変数を参照渡しにしていますが、参照変数の場合もそのまま参照渡しになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class OutTest2 : MonoBehaviour { // Start is called before the first frame update void Start() { TestClass testClass; // refを付けて引数を渡す Increase(out testClass); // StartメソッドのintValueの表示 Debug.Log(testClass.Age); } void Increase(out TestClass testClass) { testClass = new TestClass(); testClass.Age = 10; testClass.Age++; } private class TestClass { public int Age { get; set; } } } |
StartメソッドではTestClassの変数testClassの宣言をするだけで実体は生成せずIncreaseメソッドにout付きの引数として渡します。
Increaseメソッド内でTestClassの実体を作成しプロパティのAgeに10を入れた後インクリメントします。
このスクリプトを実行するとコンソールに11が表示されます。
この例はこの記事の最初に示したPhysics.Raycastの例と使い方としては同じ感じですね。
終わりに
参照渡しをする際に使用するrefとoutについてはPhysics.Raycastの引数等で見ていたのでなんとなく何をしているのかはわかっていましたが、refやoutが実際にどういった動作をするのかを見ていけたのでよかったです。
わたくしが持っている本
ではrefやoutを使ったメソッドはほとんど使う必要がない事が書かれていましたし、個人的に使う事もなかったので動作がどんなものなのかがわかるだけでいいかなと思いました。