WisdomSoft - for your serial experiences.

参照パラメータと出力パラメータ

通常、メソッドのパラメータには呼び出し元の引数を複製した値が渡されますが、変数の参照を渡すこともできます。参照パラメータを用いることで、メソッド内から呼び出し元の変数を変更できます。

値パラメータ

メソッドのパラメータに渡された引数は、その値が複製されます。この操作は変数に値を代入することと同じです。メソッドのパラメータは複製した値を格納する独立したローカル変数であるため、パラメータに対して値を代入しても、呼び出し元には影響しません。このような、通常のメソッドパラメータのことを正確には値パラメータ(value parameter)と呼びます。

コード1
class CopyHelper
{
    public void Copy(int destination, int source)
    {
        System.Console.WriteLine("destination=" + destination + ", source=" + source);
        destination = source;
    }
}

class Test
{
    public static void Main(string[] args)
    {
        CopyHelper obj = new CopyHelper();

        int i = 481;
        obj.Copy(i, 765);
        System.Console.WriteLine("result=" + i);
    }
}
実行結果
コード1 実行結果

コード1の CopyHelper クラスの Copy() メソッドは、メソッド内で destination パラメータに対し source パラメータの値を代入しています。Main() メソッドから Copy() メソッドの呼び出し時に destination パラメータに i 変数を渡しています。しかし i 変数の値が変わることはありません。destination パラメータ Copy() メソッドのローカル変数にすぎないので、その変更は Copy() 変数内では有効ですが、呼び出し元である Main() メソッドには影響しません。

これは、文字列や任意のクラスのオブジェクトに対して行っても同じです。クラス型の変数は参照型であることは参照型と値型で前述しましたが、これは変数がオブジェクトが保存されているメモリの場所を表す値を保存しているという意味です。クラス型のパラメータを書き換えても、呼び出し元の変数は影響をうけません。

コード2
class MagicalGirl
{
    public string Name { get; set; }
}

class ProjectF
{
    public void Copy(MagicalGirl destination, MagicalGirl source)
    {
        System.Console.WriteLine("Destination=" + destination.Name + ", Source=" + source.Name);
        destination = source;
    }
}

class Test
{
    public static void Main(string[] args)
    {
        MagicalGirl alicia = new MagicalGirl();
        MagicalGirl fate = new MagicalGirl();

        alicia.Name = "アリシア・テスタロッサ";
        fate.Name = "フェイト・テスタロッサ";
        
        ProjectF obj = new ProjectF();
        obj.Copy(fate, alicia);
        System.Console.WriteLine("fate=" + fate.Name + ", alicia=" + alicia.Name);
    }
}
実行結果
コード2 実行結果

コード2の ProjectF クラスは MagicalGirl クラスのオブジェクトを受け取る Copy() メソッドを公開しています。Main() メソッドで 2 つの MagicalGirl オブジェクトを生成し Copy() メソッドに渡しています。Copy() メソッドでは destination パラメータに source パラメータの値を代入していますが、やはりコード1と同様にローカル変数の書き換えにすぎないため、呼び出し元である Main() メソッド内の変数には影響がありません。

一方でメソッド内で行われたオブジェクトの変更は、メソッドの呼び出し元にも影響します。上のコード2を以下のように変更すれば Copy() メソッドによって行われた操作が Main() メソッドにまで影響することが確認できます。

destination.Name = source.Name;

この場合、パラメータの値を書き換えてるのではなく、パラメータが指しているオブジェクトのプロパティを変更しています。よって、destination パラメータが指しているオブジェクトの Name プロパティが source パラメータが指しているオブジェクトの Name プロパティの値に書き換わります。

参照パラメータ

通常のメソッドのパラメータ、すなわち値パラメータには引数に指定された値がパラメータに複製されます。これに対して、参照パラメータ(reference parameter)を用いることで呼び出し元の引数に指定された変数そのものをメソッドに渡すことができます。参照パラメータを用いれば、メソッド内から呼び出し元の変数を操作することができ、整数のような値型も間接的に受け渡しできるようになります。

参照パラメータを宣言するには ref キーワードを用いてパラメータを修飾します。

参照パラメータ宣言
ref  パラメータ名

参照パラメータは、メソッド宣言のパラメータに上記のような ref 修飾子を指定します。この場合、宣言されたパラメータは参照パラメータであると解釈されます。例えば int 型の参照パラメータを持つメソッドは以下のように宣言します。

public void M(ref int r) { ... }

上記の r パラメータは参照パラメータです。このパラメータは呼び出し元で指定された何らかの変数の参照であることが保証されます。メソッド内のコードにとって r パラメータは、これまでと同様に int 型のローカル変数のように機能しますが、その実態は呼び出し元の変数への参照なので、r パラメータの変更は、呼び出し元の変数にも影響します。

参照パラメータを持つメソッドを呼び出すには、参照パラメータに対応する引数にも ref キーワードを指定しなければなりません。また、参照パラメータは変数を指すため、引数に指定する値は割り当て可能な変数でなければなりません。リテラルなど、変数ではない値を参照パラメータの引数に指定することはできません。

参照引数
ref 変数

例えば、上記の M() メソッドを呼び出すには、参照パラメータである r パラメータに対応する引数に ref キーワードを付加しなければなりません。

int i = 0;
M(ref i);

これは、ローカル変数として宣言されている i 変数の参照を M() メソッドに渡すことを表します。よって M() メソッドに渡された参照パラメータの値を変更することは、呼び出し元である i 変数を変更することと同じです。参照パラメータは、引数に指定された呼び出し元の変数の別名であると考えることもできます。 

コード3
class CopyHelper
{
    public void Copy(ref int destination, int source)
    {
        System.Console.WriteLine("destination=" + destination + ", source=" + source);
        destination = source;
    }
}

class Test
{
    public static void Main(string[] args)
    {
        CopyHelper obj = new CopyHelper();

        int i = 481;
        obj.Copy(ref i, 765);
        System.Console.WriteLine("result=" + i);
    }
}
実行結果
コード3 実行結果

コード3コード1を改良し Copy() メソッドの destination パラメータを参照パラメータに変更しています。呼び出し元である Main() メソッドから i 変数を destination パラメータに渡しています。 実行結果から確認できるように、Copy() メソッド内で参照パラメータである destination パラメータの値を source パラメータの値で書き換えています。その結果、destination パラメータが参照している呼び出し元の i 変数の値も書き換わっています。

出力パラメータ

参照パラメータは引数に指定された変数そのものを渡すようなものでした。これを応用すれば、戻り値を用いずに参照パラメータを用いてメソッドの結果を呼び出し元に返すことができそうです。パラメータから呼び出し元に結果を返すには参照パラメータでも可能ですが、通常は出力パラメータ(output parameter)を使います。出力パラメータは参照に値を保存することを約束します。つまり、戻り値のように呼び出し元の変数に何らかの結果を返すための専用のパラメータが出力パラメータです。

未割当のローカル変数を参照パラメータの引数に指定することはできません。この場合、他の一般的な式と同じように未割当のローカル変数が使用されたとしてコンパイルエラーとなってしまいます。例えば、参照パラメータを持つ M() メソッドを以下のように呼び出すことはできません。

int result;
M(ref result); //エラー

この場合 result 変数は初期化されていない未割当のローカル変数です。一方で、出力パラメータに対してならば未割当のローカル変数の引数として利用できます。それ以外は、参照パラメータと大きな違いはありません。

出力パラメータの宣言には ref キーワードの代わりに out キーワードを用いてパラメータを修飾します。

出力パラメータ宣言
out  パラメータ名

出力パラメータは、メソッド宣言のパラメータに上記のような out 修飾子を指定します。この場合、宣言されたパラメータは出力パラメータであると解釈されます。例えば int 型の出力パラメータを持つメソッドは以下のように宣言します。

public void M(out int r) { ... }

上記の r パラメータは出力パラメータです。このパラメータは呼び出し元で指定された何らかの割り当て可能な変数への参照であり、何らかの結果を受け取るために渡されていると解釈されます。したがって、メソッドは終了までに出力パラメータに新しい値を割り当てなければなりません。

出力パラメータを持つメソッドを呼び出すには、出力パラメータに対応する引数にも out キーワードを指定しなければなりません。

出力引数
out 変数

参照パラメータに対する引数とは異なり、出力パラメータに指定する変数は初期化されていなくても問題ありません。コンパイラは、このメソッドの呼び出しで変数に値が割り当てられると解釈します。上の M() メソッドを呼び出すには、以下のようなコードを書きます。

int result;
M(out result); //OK

この場合 result 変数は初期化されていない未割当のローカル変数ですが、出力パラメータに対する引数に指定することができます出力パラメータは値を受け取ることではなく、参照を通じて呼び出し元の変数に値を設定することが目的であるため、初期化されていなくても問題ありません。逆に、メソッド内に出力パラメータから値を読み取るようなコードを書くべきではありません。

コード4
class MagicalGirl
{
    public string Name { get; set; }
}

class ProjectF
{
    public void Copy(out MagicalGirl destination, MagicalGirl source)
    {
        destination = new MagicalGirl();
        destination.Name = source.Name;
    }
}

class Test
{
    public static void Main(string[] args)
    {
        MagicalGirl alicia = new MagicalGirl();
        MagicalGirl fate;

        alicia.Name = "アリシア・テスタロッサ";

        ProjectF obj = new ProjectF();
        obj.Copy(out fate, alicia);
        System.Console.WriteLine("fate=" + fate.Name + ", alicia=" + alicia.Name);
    }
}
実行結果
コード4 実行結果

コード4コード2を出力パラメータを持つ Copy() メソッドに改良しています。Copy() メソッドでは、最初に新しい MagicalGirl インスタンスを生成し、参照パラメータである destination パラメータに代入しています。その後、source パラメータに指定された MagicalGirl オブジェクトの Name プロパティを、新しく生成したインスタンスの Name プロパティに代入しています。

destination パラメータは呼び出し元の引数で指定された変数への参照なので、この結果は呼び出し元に反映されます。Main() メソッドでは初期化していない fate 変数を destination パラメータに渡していますが、出力パラメータなのでエラーになることはありません。出力パラメータに変数を渡したため、この Copy() メソッドによって fate 変数が割り当てられたと解釈されます。出力結果を見れば Copy() メソッドで生成したインスタンスが、正しく Main() メソッドの fate 変数に格納されていることが確認できます。