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. 這樣一來,以傳址方式傳回的方法就不能有傳回型別 voidIt 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."

  • 傳回值不能是常值 nullThe 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 返回的方法可以將別名返回到其值當前為空(未具現化)值或數值型別的空數值型別的變數。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.

定義 ref 傳回值Defining a ref return value

傳回參考傳回值的方法必須滿足下列兩個條件: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.

下列範例顯示滿足那些條件並傳回參考給名為 pPerson 物件的方法: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. 在下列指派中,pGetContactInformation 所傳回之變數的別名。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. 例如,下列陳述式示範了如何定義用於參考值的區域變數值。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 之前,無法重新指派 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. 呼叫者接著會將方法所傳回的值加倍。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 方法以使用 ref 本機重新指派: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