物件 - 建立類型的實例

類別或結構定義就像藍圖,指定類型可以執行的動作。 物件是程式根據藍圖分配並配置的記憶體區塊。 程式可能會建立許多相同類別的物件。 你也可以把物件稱為實例。 你可以將它們儲存在命名變數中,或是陣列或集合中。 用戶端程式碼會使用這些變數來呼叫方法並存取物件的公開屬性。 在 C# 之類的面向物件語言中,一般程式是由多個以動態方式互動的物件所組成。

備註

靜態型態的行為與本文所述不同。 如需詳細資訊,請參閱靜態類別和靜態類別成員。

結構體實例與類別實例的比較

因為類別是參考型別,所以類別物件的變數會保存對受控堆疊上物件位址的參考。 如果你將同類型的第二個變數指派給第一個變數,兩個變數都會指向該位址的物件。 本文稍後將更詳細討論此點。

你可以使用new運算子來建立類別的實例。 在下列範例中,Person 是型別,person1 和 person2 是該類型的實例或物件。

using System;

public class Person(string name, int age)
{
    public string Name { get; set; } = name;
    public int Age { get; set; } = age;
    // Other properties, methods, events...
}

class Program
{
    static void Main()
    {
        Person person1 = new("Leopold", 6);
        Console.WriteLine($"person1 Name = {person1.Name} Age = {person1.Age}");

        // Declare new person, assign person1 to it.
        Person person2 = person1;

        // Change the name of person2, and person1 also changes.
        person2.Name = "Molly";
        person2.Age = 16;

        Console.WriteLine($"person2 Name = {person2.Name} Age = {person2.Age}");
        Console.WriteLine($"person1 Name = {person1.Name} Age = {person1.Age}");

        /*
            Output:
            person1 Name = Leopold Age = 6
            person2 Name = Molly Age = 16
            person1 Name = Molly Age = 16
        */
    }
}

因為結構是實值型別,因此結構物件的變數會保存整個對象的複本。 你也可以使用運算 new 子來建立結構體實例,但不必使用,如下範例所示:

using System;

namespace Example
{
    public struct Person
    {
        public string Name;
        public int Age;
        public Person(string name, int age)
        {
            Name = name;
            Age = age;
        }
    }

    public class Application
    {
        static void Main()
        {
            // Create  struct instance and initialize by using "new".
            // Memory is allocated on thread stack.
            Person p1 = new("Alex", 9);
            Console.WriteLine($"p1 Name = {p1.Name} Age = {p1.Age}");

            // Create  new struct object. Note that  struct can be initialized
            // without using "new".
            Person p2 = p1;

            // Assign values to p2 members.
            p2.Name = "Spencer";
            p2.Age = 7;
            Console.WriteLine($"p2 Name = {p2.Name} Age = {p2.Age}");

            // p1 values remain unchanged because p2 is  copy.
            Console.WriteLine($"p1 Name = {p1.Name} Age = {p1.Age}");
        }
    }
    /*
        Output:
        p1 Name = Alex Age = 9
        p2 Name = Spencer Age = 7
        p1 Name = Alex Age = 9
    */
}

執行緒堆疊會為 p2 和 p1 配置記憶體。 程式會回收該記憶體以及你宣告的類型或方法。 這種記憶體管理是結構體在指派時會被複製的原因之一。 相較之下,當物件的所有參考都離開作用域時,通用語言執行平台會自動回收(垃圾回收)其為類別執行個體配置的記憶體。 你不能像 C++ 那樣確定性地銷毀類別物件。 如需 .NET 中垃圾收集的詳細資訊,請參閱 垃圾收集。

備註

通用語言執行平台高度優化了在受控堆積上的記憶體配置與解除配置。 在大部分情況下,在堆積上配置類別實例與在堆疊上配置結構實例的效能成本沒有顯著差異。

物件同一性與價值等價

當你比較兩個物件以求相等時,首先決定你是否想知道這兩個變數在記憶體中是否代表同一物件,或是它們其中一個或多個欄位的值是否等價。 如果你想比較值,請考慮這些物件是值型別(結構體)或參考型別(類別、代理、陣列)的實例。

  • 使用靜態 Object.ReferenceEquals 方法判斷兩個類別實例是否指向相同的記憶體位置(即它們具有相同的 身份)。 (System.Object 是所有實值類型和參考型別的隱含基類,包括使用者定義的結構與類別。

  • 預設情況下,該 ValueType.Equals 方法會判斷兩個結構體實例中的實例欄位值是否相同。 由於所有結構都會隱含繼承自 System.ValueType,因此您會直接在 物件上呼叫 方法,如下列範例所示:

    // Person is defined in the previous example.
    
    //public struct Person(string name, int age)
    //{
    //    public string Name { get; set; } = name;
    //    public int Age { get; set; } = age;
    //}
    
    Person p1 = new("Wallace", 75);
    Person p2 = new("", 42);
    p2.Name = "Wallace";
    p2.Age = 75;
    
    if (p2.Equals(p1))
        Console.WriteLine("p2 and p1 have the same values.");
    
    // Output: p2 and p1 have the same values.
    

    預設的System.ValueTypeEquals 實作在某些情況下會使用裝箱和反射。 關於如何提供針對你的類型專屬的高效等號演算法,請參見「 當型別無法成為記錄時自行實作等號」。 記錄是參考型別,但使用值語意來實現相等性。

  • 若要判斷兩個類別實例中的欄位值是否相等,您可以使用 Equals 方法或 == 運算子。 不過,只有在類別已覆寫或多載它們時,才使用它們,以提供該類型物件「相等」所代表的自定義定義。 類別也可能實作 IEquatable<T> 介面或 IEqualityComparer<T> 介面。 這兩個介面都提供可用來測試值相等的方法。 設計會覆寫 Equals 的自訂類別時,請務必遵循 當類型無法成為 record 時自行實作相等性 和 Object.Equals(Object) 中所述的指導方針。

欲了解更多資訊,請參閱: