內建參考型別 (C# 參考)

C# 有許多內建參考類型。 它們具有關鍵字或運算子,是 .NET 程式庫中類型的同義字。

物件型別

object 類型是 System.Object 在 .NET 中的別名。 在 C# 的統一型別系統中,所有類型 (預先定義和使用者定義的、參考型別和實值型別) 都會直接或間接繼承自 System.Object。 您可以將任何型別的值指派給 object 型別的變數。 任何 object 變數都可以使用常值 null 指派給其預設值。 當實值型別的變數轉換成 物件時,即稱為 Boxed。 當類型的 object 變數轉換成實值型別時,即稱為 未收件匣。 如需詳細資訊,請參閱 Boxing 和 Unboxing

字串型別

string 類型代表零或多個 Unicode 字元序列。 stringSystem.String 在 .NET 中的別名。

雖然 string 是參考型別,但是會定義等號比較運算子 ==!= 來比較 string 物件的值,而不是參考。 以值為基礎的相等讓測試字串相等更為直覺。 例如:

string a = "hello";
string b = "h";
// Append to contents of 'b'
b += "ello";
Console.WriteLine(a == b);
Console.WriteLine(object.ReferenceEquals(a, b));

上一個範例會顯示 「True」 和 「False」,因為字串的內容相等,但 ab 不會參考相同的字串實例。

+ 運算子會串連字串:

string a = "good " + "morning";

上述程式碼會建立包含「good morning」 的字串物件。

字串 是不可變的,字串物件的內容在建立物件之後無法變更,雖然語法會讓它看起來就像可以一樣。 例如,當您撰寫此程式碼時,編譯器實際上會建立新的字串物件,以保存新序列的字元,並且會將新物件指派給 b。 已配置給 b 的記憶體 (當其包含字串 "h" 時) 即適用於記憶體回收。

string b = "h";
b += "ello";

[]運算子可用於唯讀存取字串的個別字元。 有效的索引值從 開始 0 ,且必須小於字串的長度:

string str = "test";
char x = str[2];  // x = 's';

同樣地, [] 運算子也可以用來逐一查看字串中的每個字元:

string str = "test";

for (int i = 0; i < str.Length; i++)
{
  Console.Write(str[i] + " ");
}
// Output: t e s t

字串常值

字串常值的類型 string 為 ,而且可以三種形式撰寫:原始、引號和逐字。

原始字串常值 從 C# 11 開始可供使用。 原始字串常值可以包含任意文字,而不需要逸出序列。 原始字串常值可以包含空白字元和新行、內嵌引號和其他特殊字元。 原始字串常值至少以三個雙引號括住, (「」「) :

"""
This is a multi-line
    string literal with the second line indented.
"""

您甚至可以包含三個 (或更多) 雙引號字元的序列。 如果您的文字需要內嵌引號序列,您可以視需要以更多引號開始和結束原始字串常值:

"""""
This raw string literal has four """", count them: """" four!
embedded quote characters in a sequence. That's why it starts and ends
with five double quotes.

You could extend this example with as many embedded quotes as needed for your text.
"""""

原始字串常值通常會在與內嵌文字不同的行上,有開頭和結束引號序列。 多行原始字串常值支援本身加上引號字串的字串:

var message = """
"This is a very important message."
""";
Console.WriteLine(message);
// output: "This is a very important message."

當開始和結尾引號位於不同的行時,結尾引號後面的分行符號和結尾引號前面不會包含在最終內容中。 結尾引號序列會指定字串常值的最左邊資料行。 您可以縮排原始字串常值,以符合整體程式碼格式:

var message = """
    "This is a very important message."
    """;
Console.WriteLine(message);
// output: "This is a very important message."
// The leftmost whitespace is not part of the raw string literal

結尾引號序列右邊的資料行會保留。 這可啟用 JSON、YAML 或 XML 等資料格式的原始字串,如下列範例所示:

var json= """
    {
        "prop": 0
    }
    """;

如果任何文字行延伸至結尾引號序列的左邊,編譯器就會發出錯誤。 開頭和結尾引號序列可以位於同一行,提供字串常值開頭或結尾都不能加上引號字元:

var shortText = """He said "hello!" this morning.""";

您可以將原始字串常值與 字串插補 結合,以在輸出字串中包含引號字元和大括弧。

以引號括住的字串常值是以雙引號 (") 括住:

"good morning"  // a string literal

字串常值可以包含任何字元常值。 包含逸出序列。 下列範例使用逸出序列 \\ 表示反斜線、\u0066 表示字母 f,而 \n 表示新行字元。

string a = "\\\u0066\n F";
Console.WriteLine(a);
// Output:
// \f
//  F

注意

逸出代碼 \udddd (其中 dddd 是四位數字) 代表 Unicode 字元 U+dddd。 也會辨識八位數 Unicode 逸出代碼︰\Udddddddd

逐字字串常值的開頭為 @,也會用雙引號括住。 例如:

@"good morning"  // a string literal

逐字字串的優點是 不會 處理逸出序列,這可讓您輕鬆地撰寫。 例如,下列文字元合完整Windows檔案名:

@"c:\Docs\Source\a.txt"  // rather than "c:\\Docs\\Source\\a.txt"

若要在 @-quoted 字串中包含雙引號,請將它加雙引號:

@"""Ahoy!"" cried the captain." // "Ahoy!" cried the captain.

委派型別

委派類型的宣告類似方法簽章。 它具有傳回值以及任意類型之任何數目的參數:

public delegate void MessageDelegate(string message);
public delegate int AnotherDelegate(MyType m, long num);

在 .NET 中,System.ActionSystem.Func 型別提供許多常見委派的泛型定義。 您多半不需要定義新的自訂委派型別。 反之,您可以建立所提供之泛型型別的具現化。

delegate 是可用來封裝具名或匿名方法的參考型別。 委派類似 C++ 中的函式指標,但委派是型別安全而且安全的。 如需委派的應用程式,請參閱委派泛型委派。 委派是事件的基礎。 委派的具現化方式是將它與具名或匿名方法建立關聯。

委派必須使用具有相容傳回型別和輸入參數的方法或 Lambda 運算式進行具現化。 如需方法簽章中所允許變異程度的詳細資訊,請參閱 Variance in Delegates (委派中的變異數)。 若與匿名方法搭配使用,則會一起宣告要與它建立關聯的委派和程式碼。

當執行時間涉及的委派類型因變異轉換而不同時,委派組合和移除會失敗,且執行時間例外狀況不同。 下列範例示範失敗的情況:

Action<string> stringAction = str => {};
Action<object> objectAction = obj => {};
  
// Valid due to implicit reference conversion of
// objectAction to Action<string>, but may fail
// at run time.
Action<string> combination = stringAction + objectAction;

您可以藉由建立新的委派物件,建立具有正確執行時間類型的委派。 下列範例示範如何將此因應措施套用至上述範例。

Action<string> stringAction = str => {};
Action<object> objectAction = obj => {};
  
// Creates a new delegate instance with a runtime type of Action<string>.
Action<string> wrappedObjectAction = new Action<string>(objectAction);

// The two Action<string> delegate instances can now be combined.
Action<string> combination = stringAction + wrappedObjectAction;

從 C# 9 開始,您可以宣告函式 指標,其使用類似的語法。 函式指標會使用 指令, calli 而不是具現化委派類型並呼叫虛擬 Invoke 方法。

動態型別

dynamic 型別指出使用變數和對其成員的參考,會略過編譯時間型別檢查。 相反地,這些作業會在執行階段解決。 dynamic 類型會簡化 Office Automation API 這類 COM API 的存取、IronPython 程式庫這類動態 API 的存取,以及 HTML 文件物件模型 (DOM) 的存取。

在大多數情況下,dynamic 類型的行為與 object 類型類似。 特別是,任何非 Null 運算式都可轉換成 dynamic 型別。 此 dynamic 類型與包含 型 dynamic 別運算式的作業不同 object ,編譯器不會解析或檢查類型。 編譯器會將作業資訊封裝在一起,而且稍後在執行階段會使用這項資訊來評估作業。 在此程序期間,會將 dynamic 類型的變數編譯為 object 類型的變數。 因此,dynamic 類型只存在於編譯時期,而非執行階段。

下列範例會對照 dynamic 類型的變數與 object 類型的變數。 若要在編譯時期驗證每個變數的類型,請將滑鼠指標放在 WriteLine 陳述式中的 dynobj 上方。 將下列程式碼複製到可使用 IntelliSense 的編輯器。 IntelliSense 會顯示「動態」來表示 dyn,並顯示「物件」來表示 obj

class Program
{
    static void Main(string[] args)
    {
        dynamic dyn = 1;
        object obj = 1;

        // Rest the mouse pointer over dyn and obj to see their
        // types at compile time.
        System.Console.WriteLine(dyn.GetType());
        System.Console.WriteLine(obj.GetType());
    }
}

WriteLine 陳述式會顯示執行階段類型 dynobj。 此時,兩者都有相同的類型:整數。 此時會產生下列輸出:

System.Int32
System.Int32

若要查看 dynobj 在編譯時期的差異,請在上述範例的宣告與 WriteLine 陳述式之間新增下列兩行。

dyn = dyn + 3;
obj = obj + 3;

嘗試新增運算式 obj + 3 中的整數和物件時報告編譯器錯誤。 不過,不會回報 dyn + 3 的錯誤。 在編譯時期不會檢查包含 的 dyn 運算式,因為 的類型 dyndynamic

下列範例會在數個宣告中使用 dynamicMain 方法也會對照編譯時期類型檢查與執行階段類型檢查。

using System;

namespace DynamicExamples
{
    class Program
    {
        static void Main(string[] args)
        {
            ExampleClass ec = new ExampleClass();
            Console.WriteLine(ec.exampleMethod(10));
            Console.WriteLine(ec.exampleMethod("value"));

            // The following line causes a compiler error because exampleMethod
            // takes only one argument.
            //Console.WriteLine(ec.exampleMethod(10, 4));

            dynamic dynamic_ec = new ExampleClass();
            Console.WriteLine(dynamic_ec.exampleMethod(10));

            // Because dynamic_ec is dynamic, the following call to exampleMethod
            // with two arguments does not produce an error at compile time.
            // However, it does cause a run-time error.
            //Console.WriteLine(dynamic_ec.exampleMethod(10, 4));
        }
    }

    class ExampleClass
    {
        static dynamic field;
        dynamic prop { get; set; }

        public dynamic exampleMethod(dynamic d)
        {
            dynamic local = "Local variable";
            int two = 2;

            if (d is int)
            {
                return local;
            }
            else
            {
                return two;
            }
        }
    }
}
// Results:
// Local variable
// 2
// Local variable

另請參閱