참조 반환 및 참조 로컬Ref returns and ref locals

C# 7.0부터 C#에서 참조 반환 값(ref return)을 지원합니다.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. 호출자는 참조 로컬이라고 하는, 반환된 값에 대한 참조 자체인 새 변수를 만들 수 있습니다.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’ 로컬은 참조 로컬이 아니므로 참조로 반환할 수 없습니다.”가 생성됩니다.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."

    참조 반환이 있는 메서드는 값이 현재 null(인스턴스화되지 않음) 값이거나 값 형식이 nullable 값 형식인 변수에 별칭을 반환할 수 있습니다.A method with a ref return can return an alias to a variable whose value is currently the null (uninstantiated) value or a nullable value type for a value type.

  • 반환 값은 상수, 열거형 멤버, 속성의 값 형식 반환 값 또는 classstruct의 메서드일 수 없습니다.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.

참조 반환 값 정의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;
}

참조 반환 값 사용Consuming a ref return value

참조 반환 값은 호출된 메서드 범위 내 다른 변수에 대한 별칭입니다.The ref return value is an alias to another variable in the called method's scope. 참조 반환의 사용은 다음과 같이 별칭이 있는 변수를 사용하는 것으로 해석할 수 있습니다.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.
  • 참조 로컬 별칭을 만들 경우 동일한 변수에 대한 새 별칭을 만드는 것입니다.When you make a ref local alias, you make a new alias to the same variable.

참조 로컬Ref locals

GetContactInformation 메서드가 다음과 같이 참조 반환으로 선언된다고 가정합니다.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.

‘참조 로컬’ 변수를 선언하여 원래 값에 대한 별칭을 복사합니다. You declare a ref local variable to copy the alias to the original value. 다음 할당에서 pGetContactInformation에서 반환된 변수에 대한 별칭입니다.In the following assignment, p is an alias to the variable returned from GetContactInformation.

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

p가 해당 변수의 별칭이므로 이후 p 사용은 GetContactInformation에서 반환된 변수를 사용하는 것과 같습니다.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. 예를 들어, 다음 명령문은 값을 참조하는 데 사용되는 참조 로컬 값을 정의하는 방법을 보여줍니다.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 키워드는 지역 변수 선언 앞 ‘그리고’ 두 번째 예의 값 앞에 사용됩니다. 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 이전에 참조 로컬 변수는 초기화되기 전에 다른 스토리지 공간을 참조하도록 다시 할당될 수 없었습니다.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 local variables must still be initialized when they are declared.

참조 반환 및 참조 로컬: 예제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. 그런 다음 호출자가 메서드에서 반환된 값을 두 배로 만듭니다.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 이후에 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);
}

이 두 번째 버전은 검색된 수가 배열의 끝에 더 가까운 시나리오에서 더 긴 시퀀스를 사용하여 더욱 효율적입니다.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