翻譯自 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.ValueType
,System.ValueType
又繼承於System.Object
。 -
結構體不能是基類,因此,結構體不能是
abstract
的,且總是隱式密封的(sealed
)。 -
不允許對結構體使用抽象(
abstract
)和密封(sealed
)修飾符,也不允許對結構體成員使用protected
或protected 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 | 在結構體中,每個變量都包含自己的數據副本(ref 和 out 參數變量除外),對一個變量的操作不會影響另一個變量。 |
在類中,兩個變量可以包含同一對象的引用,對一個變量的任何操作都會影響另一個變量。 |
這樣,結構體(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.ValueType
的 Equals
是使用反射實現的,因為它必須能夠確定任何結構體中有哪些字段。 在創建自己的結構體時,重寫 Equals
方法可以提供特定於你的類型的高效求等算法。
“基於值的相等”這一點和 C# 9.0 中新增的記錄(record
) 類型具有相似之處,想了解 C# 9.0 可以查看:歡迎來到 C# 9.0。