宣告陳述式

宣告語句會宣告新的變數,並選擇性地初始化它。 所有變數都具有宣告類型。 您可以在 .NET 類型系統上的文章中深入瞭解類型。 一般而言,宣告包含類型和變數名稱。 它也可以包含初始化: = 運算子後面接著運算式。 型別可能會取代為 var 。 宣告或運算式可能包含 ref 修飾詞,以宣告新變數參考現有的儲存位置。

隱含型別區域變數

在方法範圍中宣告的變數可以有隱含的「類型」 var 。 隱含類型的區域變數是強型別,就像您自己宣告類型一樣,但編譯器會決定類型。 和 的下列兩個宣告 ab 在功能上相等:

var a = 10; // Implicitly typed.
int b = 10; // Explicitly typed.

重要

var 與啟用 可為 Null 的參考型別 搭配使用時,它一律表示可為 Null 的參考型別,即使運算式類型不可為 Null 也一樣。 編譯器的 Null 狀態分析可防止取值可能 null 的值。 如果變數從未指派給可能為 null 的運算式,編譯器就不會發出任何警告。 如果您將變數指派給可能為 Null 的運算式,則必須在取值之前先測試它不是 Null,以避免任何警告。

關鍵字的 var 常見用法是搭配建構函式調用運算式。 使用 var 可讓您在變數宣告和物件具現化中重複類型名稱,如下列範例所示:

var xs = new List<int>();

從 C# 9.0 開始,您可以使用目標型別new 運算式作為替代方法:

List<int> xs = new();
List<int>? ys = new();

在模式比對中 var ,關鍵字會用於var 模式中。

下例示範兩個查詢運算式。 在第一個運算式中,允許使用 var ,但並非必要,因為查詢結果的類型可以明確陳述為 IEnumerable<string> 。 不過,在第二個運算式中, var 允許結果成為匿名型別的集合,而且除了編譯器本身之外,無法存取該類型的名稱。 使用 var 就不需要建立結果的新類別。 在範例 #2 中 foreach ,反復專案變數 item 也必須隱含類型。

// Example #1: var is optional when
// the select clause specifies a string
string[] words = { "apple", "strawberry", "grape", "peach", "banana" };
var wordQuery = from word in words
                where word[0] == 'g'
                select word;

// Because each element in the sequence is a string,
// not an anonymous type, var is optional here also.
foreach (string s in wordQuery)
{
    Console.WriteLine(s);
}

// Example #2: var is required because
// the select clause specifies an anonymous type
var custQuery = from cust in customers
                where cust.City == "Phoenix"
                select new { cust.Name, cust.Phone };

// var must be used because each item
// in the sequence is an anonymous type
foreach (var item in custQuery)
{
    Console.WriteLine("Name={0}, Phone={1}", item.Name, item.Phone);
}

ref 區域變數

您可以在變數類型之前新增 ref 關鍵字,以宣告 ref 區域變數。 假設方法 GetContactInformation 宣告為 ref 傳回

public ref Person GetContactInformation(string fname, string lname)

傳值方式指派會讀取變數值,並將它指派給新的變數:

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

上述的指派將 p 宣告為區域變數。 其初始值的複製來源,是讀取 GetContactInformation 所傳回的值。 的任何未來指派 p 都不會變更 所 GetContactInformation 傳回之變數的值。 變數 p 不再是所傳回之變數的別名。

您可以宣告 ref 變數,以將別名複製到原始值。 在下列指派中,pGetContactInformation 所傳回之變數的別名。

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

後續 p 的使用方式與使用 GetContactInformation 傳回的變數相同,因為 p 是該變數的別名。 變更 p 也會變更 GetContactInformation 傳回的變數。

您可透過相同方式以參考存取值。 在某些情況下,以參考存取值會避免潛在過度浪費資源的複製作業,進而增加效能。 例如,下列陳述式示範了如何定義用於參考值的區域變數值。

ref VeryLargeStruct reflocal = ref veryLargeStruct;

區域變數宣告的前面「和」第二個範例中值的前面都會使用 ref 關鍵字。 在兩個範例中,若未在變數宣告中包含這兩 ref 個關鍵字,則會導致編譯器錯誤 CS8172:「無法以值初始化位元組參考變數」。

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

在宣告參考區域變數時,仍必須初始化它們。

下列範例定義 NumberStore 類別,以儲存整數值的陣列。 FindNumber 方法會以傳址方式傳回第一個數字,而此數字大於或等於傳遞為引數的數字。 如果數字未大於或等於引數,則方法會在索引 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 的第一個值。 呼叫者接著會將方法所傳回的值加倍。 範例輸出顯示 NumberStore 執行個體之陣列項目值中所反映的變更。

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

如果不支援參考傳回值,則是透過傳回陣列項目和其值的索引來執行這類作業。 呼叫者接著可以使用這個索引,來修改不同方法呼叫中的值。 不過,呼叫者也可以修改要存取的索引,也可能修改其他陣列值。

下列範例示範如何 FindNumber 重寫 方法,以使用 ref 本機重新指派:

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);
}

這個第二個版本在搜尋的數目較接近陣列結尾的案例中,使用較長的序列會更有效率,因為陣列從結尾到開頭逐一查看,因而會檢查較少的專案。

編譯器會在變數上 ref 強制執行範圍規則: ref 類型中的 ref struct 區域變數 ref 、參數和 ref 欄位。 這些規則可確保參考不會與參考的物件不一致。 請參閱 方法參數一文中的範圍規則一節。

ref 和 readonly

修飾 readonly 詞可以套用至 ref 區域變數和 ref 欄位。 readonly修飾詞會影響右邊的運算式。 請參閱下列範例宣告:

ref readonly int aConstant; // aConstant can't be value-reassigned.
readonly ref int Storage; // Storage can't be ref-reassigned.
readonly ref readonly int CantChange; // CantChange can't be value-reassigned or ref-reassigned.
  • value reassignment 表示已重新指派變數的值。
  • ref 指派 表示變數現在參考不同的 物件。

readonly refreadonly ref readonly 宣告只在 中的 ref struct 欄位上 ref 有效。

scoped ref

內容關鍵字 scoped 會限制值的存留期。 scoped修飾詞會將ref-safe-to-escapesafe-to-escape存留期分別限制為目前的 方法。 實際上,新增 scoped 修飾詞會判斷提示您的程式碼不會延長變數的存留期。

您可以套用 scoped 至參數或區域變數。 當 scoped 型別為 ref struct 時,修飾詞可以套用至參數和區域變數。 否則, scoped 修飾詞只能套用至 ref 類型的區域變數。 這包括以 ref 修飾詞宣告的區域變數,以及使用 inrefout 修飾詞宣告的參數。

scoped 型別為 ref struct 時,修飾詞會隱含地新增至 thisout 參數和 ref 參數中 struct 宣告的方法。

另請參閱