C# 中 Struct 和 Class 的區別總結

技術譯民發表於2020-09-17

翻譯自 Manju lata Yadav 2019年6月2日 的博文 《Difference Between Struct And Class In C#》,補充了一些內容和示例。

結構體(struct)是類(class)的輕量級版本。結構體是值型別,可用於建立行為類似於內建型別的物件。

比較

結構體和類共享許多特性,但與類相比有以下侷限性。

  • 結構體不能有預設建構函式(無參建構函式)或解構函式,建構函式中必須給所有欄位賦值。

    public struct Coords
    {
        public double x;
        public double y;
    
        public Coords() //錯誤,不允許無參建構函式
        {
            this.x = 3;
            this.y = 4;
        }
        
        public Coords(double x) //錯誤,建構函式中必須給所有欄位賦值
        {
            this.x = x;
        }
        
        public Coords(double x) //這個是正確的
        {
            this.x = x;
            this.y = 4;
        }
    
        public Coords(double x, double y) //這個是正確的
        {
            this.x = x;
            this.y = y;
        }
    }
    
  • 結構體是值型別,在賦值時進行復制。

  • 結構體是值型別,而類是引用型別。

  • 結構體可以在不使用 new 操作符的情況下例項化。
    例如:

    public struct Coords
    {
        public double x;
        public double y;
    }
    
    static void Main()
    {
        Coords p;
        p.x = 3;
        p.y = 4;
        Console.WriteLine($"({p.x}, {p.y})");  // 輸出: (3, 4)
    }
    
  • 結構體不能繼承於另一個結構體或者類,類也不能繼承結構體。所有結構體都直接繼承於抽象類 System.ValueTypeSystem.ValueType 又繼承於 System.Object

  • 結構體不能是基類,因此,結構體不能是 abstract 的,且總是隱式密封的(sealed)。

  • 不允許對結構體使用抽象(abstract)和密封(sealed)修飾符,也不允許對結構體成員使用 protectedprotected internal 修飾符。

  • 結構體中的函式成員不能是抽象的(abstract)或虛的(virtual),重寫(override)修飾符只允許重寫從 System.ValueType 繼承的方法。

  • 結構體中不允許例項屬性或欄位包含初始值設定項。但是,結構體允許靜態屬性或欄位包含初始值設定項。
    例如:

    public struct Coords
    {
        public double x = 4; //錯誤, 結構體中初始化器不允許例項欄位設定初始值
        public static double y = 5; // 正確
        public static double z { get; set; } = 6; // 正確
    }
    
  • 結構體可以實現介面。

  • 結構體可以用作 nullable type(即:Nullable<T> 中的 T),對其賦值 null 值,參考【Nullable<T> Struct

什麼時候使用結構體或類?

要回答這個問題,我們應該很好地理解它們的差異。

序號 結構體(struct 類(class)
1 結構體是值型別,可以在棧(stack)上分配,也可以在包含型別中內聯分配。 類是引用型別,在堆(heap)上分配並垃圾回收。
2 值型別的分配和釋放通常比引用型別的分配和釋放更節約成本。 大的引用型別的賦值比大的值型別的賦值成本更低。
3 在結構體中,每個變數都包含自己的資料副本(refout 引數變數除外),對一個變數的操作不會影響另一個變數。 在類中,兩個變數可以包含同一物件的引用,對一個變數的任何操作都會影響另一個變數。

這樣,結構體(struct)只能在確定以下情形時使用:

  • 它在邏輯上表示單個值,比如基本型別(int, double,等等)。
  • 它是不可變的(immutable)。
  • 它不會頻繁地裝箱和拆箱。

在所有其他情形,應該將型別定義為類(class)。

結構體示例:

struct Location
{
    public int x, y;
    public Location(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}

static void Main()
{
    Location a = new Location(20, 20);
    Location b = a;
    a.x = 100;
    Console.WriteLine(b.x);
}

輸出將是 20。“b” 的值是 “a” 的副本,因此 “b” 不受 “a.x” 更改的影響。但是在類中,輸出將是 100,因為變數 “a” 和 “b” 引用同一個物件。



以下為譯者補充

結構體例項與類例項

結構體例項的記憶體在棧(stack)上進行分配,所佔用的記憶體隨宣告它的型別或方法一起回收。 這就是在賦值時要複製結構體的一個原因。 相比之下,類例項的記憶體在堆(heap)上進行分配,當對類例項的所有引用都超出範圍時,為該類例項分配的記憶體將由公共語言執行時自動回收(垃圾回收)。

結構體例項的值相等性

兩個結構體例項的比較是基於值的比較,而類例項的比較則是對其引用的比較。

若要確定兩個結構體例項中的例項欄位是否具有相同的值,可使用 ValueType.Equals 方法。 由於所有結構體都隱式繼承於 System.ValueType,因此可以直接在其物件上呼叫該方法,如以下示例所示:

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

static void Main()
{
    Person p1 = new Person("技術譯站", 100);
    Person p2;
    p2.Name = "技術譯站";
    p2.Age = 100;

    if (p2.Equals(p1))
        Console.WriteLine("p2 和 p1 有相同的值。");

    Console.ReadKey();
}
// 輸出: p2 和 p1 有相同的值。

System.ValueTypeEquals 是使用反射實現的,因為它必須能夠確定任何結構體中有哪些欄位。 在建立自己的結構體時,重寫 Equals 方法可以提供特定於你的型別的高效求等演算法。

“基於值的相等”這一點和 C# 9.0 中新增的記錄(record) 型別具有相似之處,想了解 C# 9.0 可以檢視:歡迎來到 C# 9.0


作者 : Manju lata Yadav
譯者 : 技術譯民
出品 : 技術譯站
連結 : 英文原文

相關文章