ref 戻り値と ref ローカル変数Ref returns and ref locals

C# 7.0 以降の C# は参照戻り値 (ref 戻り値) に対応しています。Starting with C# 7.0, C# supports reference return values (ref returns). 参照戻り値を使用すると、メソッドから呼び出し元に、値ではなく、変数への参照を返すことができます。A reference return value allows a method to return a reference to a variable, rather than a value, back to a caller. 呼び出し元は、返された変数を値渡しとして処理するか、参照渡しとして処理するかを選択できます。The caller can then choose to treat the returned variable as if it were returned by value or by reference. 呼び出し元は、それ自体が、返された値の参照である新しい変数を作成できます。これは ref ローカルと呼ばれています。The caller can create a new variable that is itself a reference to the returned value, called a ref local.

参照戻り値とはWhat is a reference return value?

呼び出されたメソッドに参照によって引数を渡す参照渡しは、ほとんどの開発者によく知られています。Most developers are familiar with passing an argument to a called method by reference. 呼び出されたメソッドの引数リストには、参照によって渡された変数が含まれています。A called method's argument list includes a variable passed by reference. 呼び出されたメソッドがその値に対して行った変更は、呼び出し元によって観察されます。Any changes made to its value by the called method are observed by the caller. "参照戻り値" とは、メソッドが何らかの変数への "参照" (または別名) を返すことを意味します。A reference return value means that a method returns a reference (or an alias) to some variable. その変数のスコープには、メソッドが含まれている必要があります。That variable's scope must include the method. その変数の有効期間は、メソッドから戻った後まで継続している必要があります。That variable's lifetime must extend beyond the return of the method. 呼び出し元によるメソッドの戻り値の変更は、メソッドによって返される変数に対して行われます。Modifications to the method's return value by the caller are made to the variable that is returned by the method.

メソッドが参照戻り値を返すという宣言があれば、それはそのメソッドが変数にエイリアスを返すことを示します。Declaring that a method returns a reference return value indicates that the method returns an alias to a variable. この設計の意図は、多くの場合、呼び出し元のコードが変数の変更などを行うとき、そのコードにはエイリアス経由でその変数にアクセスさせるというものです。The design intent is often that the calling code should have access to that variable through the alias, including to modify it. 参照で返すメソッドには戻り値の型 void を与えることができません。It follows that methods returning by reference can't have the return type void.

メソッドが参照戻り値として返すことができる式には、いくつかの制限があります。There are some restrictions on the expression that a method can return as a reference return value. 次のような制約があります。Restrictions include:

  • 戻り値の有効期間は、メソッドの実行より長くならないようにする必要があります。The return value must have a lifetime that extends beyond the execution of the method. 言い換えると、値を返すメソッドに含まれているローカル変数にすることはできません。In other words, it cannot be a local variable in the method that returns it. クラスのインスタンスまたは静的フィールドを戻り値にすることができます。または、メソッドに渡される引数を戻り値にすることもできます。It can be an instance or static field of a class, or it can be an argument passed to the method. ローカル変数を返そうとすると、コンパイラ エラー CS8168 "ローカル変数 'obj' は ref ローカル変数ではないため、参照渡しで返すことはできません" が生成されます。Attempting to return a local variable generates compiler error CS8168, "Cannot return local 'obj' by reference because it is not a ref local."

  • 戻り値はリテラル null にすることができません。The return value cannot be the literal null. null を返すと、コンパイラ エラー CS8156 "参照渡しで返すことができないため、このコンテキストで使用できない式があります" が生成されます。Returning null generates compiler error CS8156, "An expression cannot be used in this context because it may not be returned by reference."

    ref 戻りを含むメソッドは、現在の値が null (インスタンス化されていない) 値か、値の型が null 許容型の変数にエイリアスを返すことができます。A method with a ref return can return an alias to a variable whose value is currently the null (uninstantiated) value or a nullable type for a value type.

  • 戻り値は、定数、列挙型のメンバー、プロパティの値渡し戻り値、class または struct のメソッドにすることができません。The return value cannot be a constant, an enumeration member, the by-value return value from a property, or a method of a class or struct. この規則に違反すると、コンパイラ エラー CS8156 "参照渡しで返すことができないため、このコンテキストで使用できない式があります" が生成されます。Violating this rule generates compiler error CS8156, "An expression cannot be used in this context because it may not be returned by reference."

さらに、参照戻り値は非同期メソッドでは許可されません。In addition, reference return values are not allowed on async methods. 非同期メソッドは実行が終了する前に戻る可能性があり、戻り値はまだ不明です。An asynchronous method may return before it has finished execution, while its return value is still unknown.

ref 戻り値の定義Defining a ref return value

"参照戻り値" を返すメソッドは、次の 2 つの条件を満たす必要があります。A method that returns a reference return value must satisfy the following two conditions:

  • メソッドのシグネチャで、戻り値の型の前にキーワード ref が含まれること。The method signature includes the ref keyword in front of the return type.
  • メソッド本体の各 return ステートメントで、返されるインスタンスの名前の前に ref キーワードが含まれること。Each return statement in the method body includes the ref keyword in front of the name of the returned instance.

これらの条件を満たし、p という名前の Person オブジェクトへの参照を返すメソッドを、次の例に示します。The following example shows a method that satisfies those conditions and returns a reference to a Person object named p:

public ref Person GetContactInformation(string fname, string lname)
{
    // ...method implementation...
    return ref p;
}

ref 戻り値の使用Consuming a ref return value

ref 戻り値は、呼び出されるメソッドの範囲で、別の変数のエイリアスになります。The ref return value is an alias to another variable in the called method's scope. ref 戻り値の使用は、それが別名を与える変数の使用として解釈できます。You can interpret any use of the ref return as using the variable it aliases:

  • その値を割り当てるとき、それが別名を与える変数に値を割り当てることになります。When you assign its value, you are assigning a value to the variable it aliases.
  • その値を読み取るとき、それが別名を与える変数の値を読み取ることになります。When you read its value, you are reading the value of the variable it aliases.
  • "参照渡し" で値を返す場合、その同じ変数の別名を返すことになります。If you return it by reference, you are returning an alias to that same variable.
  • "参照渡し" で別のメソッドに値を渡す場合、それが別名を与える変数への参照を渡すことになります。If you pass it to another method by reference, you are passing a reference to the variable it aliases.
  • ref ローカルをエイリアスにすると、同じ変数に新しいエイリアスが作られます。When you make a ref local alias, you make a new alias to the same variable.

ref ローカル変数Ref locals

GetContactInformation メソッドが ref 戻り値として宣言されているとします。Assume the GetContactInformation method is declared as a ref return:

public ref Person GetContactInformation(string fname, string lname)

値渡し代入によって、変数の値が読み取られ、それが新しい変数に渡されます。A by-value assignment reads the value of a variable and assigns it to a new variable:

Person p = contacts.GetContactInformation("Brandie", "Best");

先の代入では、ローカル変数として p が宣言されています。The preceding assignment declares p as a local variable. その初期値は、GetContactInformation によって返された値の読み取りからコピーされます。Its initial value is copied from reading the value returned by GetContactInformation. 今後、p に値が代入されることで、GetContactInformation によって返された変数の値が変わることはありません。Any future assignments to p will not change the value of the variable returned by GetContactInformation. 変数 p は、返された変数のエイリアスではなくなります。The variable p is no longer an alias to the variable returned.

ref ローカル変数を宣言し、元の値にエイリアスをコピーします。You declare a ref local variable to copy the alias to the original value. 次の代入では、p は、GetContactInformation から返された変数のエイリアスになります。In the following assignment, p is an alias to the variable returned from GetContactInformation.

ref Person p = ref contacts.GetContactInformation("Brandie", "Best");

この後 p を使用することは、GetContactInformation によって返された変数を使用することと同じです。p はその変数のエイリアスであるためです。Subsequent usage of p is the same as using the variable returned by GetContactInformation because p is an alias for that variable. p を変更すると、GetContactInformation から返される変数も変更されます。Changes to p also change the variable returned from GetContactInformation.

ref キーワードは、ローカル変数宣言の前 "および" メソッド呼び出しの前の両方で使用します。The ref keyword is used both before the local variable declaration and before the method call.

同じ方法で、参照渡しの値にアクセスできます。You can access a value by reference in the same way. 場合によっては、参照渡しの値へのアクセスによって負荷がかかる可能性があるコピー操作が回避され、パフォーマンスが向上します。In some cases, accessing a value by reference increases performance by avoiding a potentially expensive copy operation. たとえば、次のステートメントは、値の参照に使用される ref ローカル値をどのように定義できるかを示しています。For example, the following statement shows how one can define a ref local value that is used to reference a value.

ref VeryLargeStruct reflocal = ref veryLargeStruct;

ref キーワードは、ローカル変数宣言の前 "および" 2 番目の例の値の前で使用します。The ref keyword is used both before the local variable declaration and before the value in the second example. 両方の例の、変数宣言と代入の両方の ref キーワードを含めないと、コンパイラ エラー CS8172 "値を使用して参照渡し変数を初期化することはできません" が生成されます。Failure to include both ref keywords in the variable declaration and assignment in both examples results in compiler error CS8172, "Cannot initialize a by-reference variable with a value."

C# 7.3 より前は、初期化後に別の記憶域を参照するように ref ローカル変数を割り当て直すことはできませんでした。Prior to C# 7.3, ref local variables couldn't be reassigned to refer to different storage after being initialized. この制限はなくなりました。That restriction has been removed. 再割り当ての例を次に示します。The following example shows a reassignment:

ref VeryLargeStruct reflocal = ref veryLargeStruct; // initialization
refLocal = ref anotherVeryLargeStruct; // reassigned, refLocal refers to different storage.

ref ローカル変数は、宣言時にやはり初期化する必要があります。Ref local variables must still be initialized when they are declared.

ref 戻り値と ref ローカル変数: 使用例Ref returns and ref locals: an example

次の例では、整数値の配列を格納する NumberStore クラスを定義しています。The following example defines a NumberStore class that stores an array of integer values. FindNumber メソッドは、引数として渡された数値に等しいかそれより大きい最初の数値を参照渡しで返します。The FindNumber method returns by reference the first number that is greater than or equal to the number passed as an argument. 引数に等しいかそれより大きい数値がない場合、メソッドはインデックス 0 の数値を返します。If no number is greater than or equal to the argument, the method returns the number in index 0.

using System;

class NumberStore
{
    int[] numbers = { 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023 };

    public ref int FindNumber(int target)
    {
        for (int ctr = 0; ctr < numbers.Length; ctr++)
        {
            if (numbers[ctr] >= target)
                return ref numbers[ctr];
        }
        return ref numbers[0];
    }

    public override string ToString() => string.Join(" ", numbers);
}

次の例では、NumberStore.FindNumber メソッドを呼び出して、16 に等しいかそれより大きい最初の値を取得します。The following example calls the NumberStore.FindNumber method to retrieve the first value that is greater than or equal to 16. 呼び出し元は、メソッドによって返された値を 2 倍にします。The caller then doubles the value returned by the method. 次の例の出力では、NumberStore インスタンスの配列要素の値に変更が反映されたことが示されています。The output from the example shows the change reflected in the value of the array elements of the NumberStore instance.

var store = new NumberStore();
Console.WriteLine($"Original sequence: {store.ToString()}");
int number = 16;
ref var value = ref store.FindNumber(number);
value *= 2;
Console.WriteLine($"New sequence:      {store.ToString()}");
// The example displays the following output:
//       Original sequence: 1 3 7 15 31 63 127 255 511 1023
//       New sequence:      1 3 7 15 62 63 127 255 511 1023

参照戻り値がサポートされていない場合、このような操作は、配列要素のインデックスと値を返すことによって実行されます。Without support for reference return values, such an operation is performed by returning the index of the array element along with its value. 呼び出し元はこのインデックスを使用して、別のメソッド呼び出しで値を変更できます。The caller can then use this index to modify the value in a separate method call. 一方、インデックスを変更して配列の他の値にアクセスし、変更することも可能です。However, the caller can also modify the index to access and possibly modify other array values.

次の例では、C# 7.3 以降で ref ローカル変数の再割り当てを使用するように FindNumber メソッドを書き直す方法を示します。The following example shows how the FindNumber method could be rewritten after C# 7.3 to use ref local reassignment:

using System;

class NumberStore
{
    int[] numbers = { 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023 };

    public ref int FindNumber(int target)
    {
        ref int returnVal = ref numbers[0];
        var ctr = numbers.Length - 1;
        while ((ctr > 0) && numbers[ctr] >= target)
        {
            returnVal = ref numbers[ctr];
            ctr--;
        }
        return ref returnVal;
    }

    public override string ToString() => string.Join(" ", numbers);
}

この 2 番目のバージョンは、シークされる値が配列の末尾近くにあるようなシナリオのシーケンスが長い場合に、より効率的です。This second version is more efficient with longer sequences in scenarios where the number sought is closer to the end of the array.

関連項目See also