淺析C#中的結構體和類
類和結構是 .NET Framework 中的常規型別系統的兩種基本構造。 兩者在本質上都屬於資料結構,封裝著一組整體作為一個邏輯單位的資料和行為。 資料和行為是該類或結構的“成員”,它們包含各自的方法、屬性和事件等
對於C/C++程式猿來說,結構體和類的區別很小。只是結構體的預設成員變數為public,類的預設成員變數為private。
但是對於C#來說,結構體和類有很多的不同。
首先來談一談為何需要結構體:
最基本的原因就是結構體有能力去管理、使用不同資料型別的組合。
.NET支援值型別和引用型別的概念,所有的C#內建型別中,除了string外均為值型別。
在C#中,結構體是值型別,類是引用型別。值型別可以減少對堆的管理、使用,減少垃圾回收,表現出更好的效能。但是值型別也有不好的一面,比如會涉及到裝箱拆箱等操作。
下面定義一個結構體:
public struct Foo
{
// Fields
private string fooString;
private int fooNumber;
// Property
public string FooString
{
get
{
return fooString;
}
set
{
fooString = value;
}
}
// Method
public int GetFooNumber()
{
return fooNumber;
}
}
可以看到,結構體和類非常的相似。讓我們更深層次的看看兩者的不同。
1繼承
結構體繼承自System.ValueType,而類繼承自System.Object。結構體不能繼承其他的類或結構體,但是可以把結構體當做是介面。由於介面只是用於引用型別的操作,所以把結構體當成介面就會隱式的發生裝箱操作。例如如下程式碼:
struct Foo : IFoo
{
int x;
}
IFoo iFoo = new Foo();
2構造
C#不允許結構體具有無引數的預設建構函式。原因是:對於值型別,編譯器既不會生成預設建構函式,也不會呼叫預設建構函式。所以你不能這樣初始化:
struct MyWrongFoo
{
int x = 1;
}
但是你可以使用new:
Foo foo = new Foo();
這裡需要注意的是,儘管使用了new操作,但是結構體分配在棧上,而不是堆上。更有趣的是,new操作沒有呼叫無引數的建構函式。看看下面的程式碼:
struct Foo
{
int x;
public Foo(int x)
{
this.x = x;
}
}
class FooTester
{
[STAThread]
static void Main(string[] args)
{
Foo f = new Foo();
}
}
這裡我過載了建構函式,就可以使用new了。
所以我們可以這樣:
呼叫 new Foo()
呼叫過載的建構函式初始化
顯示的設定每個值:
Foo foo;
foo.x = 0;
3析構
我們不能為結構體定義解構函式。
4只讀關鍵字
對於引用型別,readonly關鍵字阻止你將引用指到其他物件,但是無法阻止你改變該物件的狀體。
對於值型別來說,readonly關鍵字與C++中的const很像,阻止你改變物件的狀態。
class MyReferenceType
{
int state;
public int State
{
get
{
return state;
}
set
{
state = value;
}
}
}
struct MyValueType
{
int state;
public int State
{
get
{
return state;
}
set
{
state = value;
}
}
}
class Program
{
readonly MyReferenceType myReferenceType = new MyReferenceType();
readonly MyValueType myValueType = new MyValueType();
public void SomeMethod()
{
myReferenceType = new MyReferenceType(); // Compiler Error
myReferenceType.State = 1234; // Ok
myValueType = new MyValueType(); // Compiler Error
myValueType.State = 1234; // Compiler Error
}
}
總結:
為結構定義預設(無引數)建構函式是錯誤的。 在結構體中初始化例項欄位也是錯誤的。 只能通過兩種方式初始化結構成員:一是使用引數化建構函式,二是在宣告結構後分別訪問成員。 對於任何私有成員或以其他方式設定為不可訪問的成員,只能在建構函式中進行初始化。
如果使用 new 運算子建立結構物件,則會建立該結構物件,並呼叫適當的建構函式。 與類不同,結構的例項化可以不使用 new 運算子。 在此情況下不存在建構函式呼叫,因而可以提高分配效率。 但是,在初始化所有欄位之前,欄位將保持未賦值狀態且物件不可用。
當結構包含引用型別作為成員時,必須顯式呼叫該成員的預設建構函式,否則該成員將保持未賦值狀態且該結構不可用。 (這將導致編譯器錯誤 CS0171。)
對於結構,不像類那樣存在繼承。 一個結構不能從另一個結構或類繼承,而且不能作為一個類的基。 但是,結構從基類 Object 繼承。 結構可實現介面,其方式同類完全一樣。