.Net的類型系統比較復雜,很多人經常給繞進來,比如《[原創]慢話interface是值類型還是引用類型》一文。而網上的、書上的關於.Net類型的表述一般是錯誤的或者不完全的,准確性最高的是MSDN上的表述,但那個表述又太簡單了,讓人很難理解。本文試着通俗的解釋幾個關於.Net類型的基礎問題。
1. 托管與非托管
像現實生活中,有體制內和體制外之分,.Net 里也有托管和非托管之分。托管堆、GC這些是托管部分,非托管堆是非托管部分。
托管部分叫做組織,體制內,非托管部分叫做群眾,體制外。
2. 引用類型是體制內的類型
有組織關系的,體制內的類型叫做引用類型。體制內的類型(引用類型)有什么特點呢?它不能脫離組織。由組織 new 它,死后魂歸組織。生命周期由組織來維護。生是組織的人,死是組織的鬼。它是受組織保護的,你不能用指針這樣粗暴野蠻危險的東西來威脅體制內的類型。
3. 值類型是體制外的類型
體制外的類型是值類型(非托管堆 或者 棧中)。
值類型要想進入體制內(托管堆),必須找到掛靠者。掛靠者必須是體制內的類型。這像貸款要擔保一樣,辦雜志要找到托管單位一樣,進入體制類,必須找到一個管理者。所以,一個值類型要想進入托管堆里,它必須是一個引用類型組成部分或者組成部分的組成部分……,它的生死存亡,和這個引用類型綁定在一起。
裝箱是一種特殊的掛靠。
體制外的類型不受體制保護,所以可以用指針這樣粗暴野蠻危險的東西來處置。
4.值類型和引用類型的區別
引用類型和值類型的區別是有沒有組織關系,是體制內的還是體制外的,而不是常說的什么棧啊堆啊的blablabla。
引用類型是體制內的,一生都在體制內呆着。而值類型是體制外的人,平時在棧里和非托管堆里生存,想進體制內得掛靠體制內的人。
由於身份的不同,所受的待遇也不同。這就好比,一個群眾和一個官員去辦事,所受到的待遇不同一個道理。
5.辦事大廳——棧
辦事(函數調用)在棧里進行。
6.辦事待遇
體制內的和體制外的辦事待遇(傳參)不一樣。
組織里的人辦事只用打電話就行了,不用親自去。當然,他也沒辦法親自去,他的腦袋、胳膊、腿都是組織的,都在托管堆上,托管堆不會放他走,他根本去不了。而體制外的人辦事得親自去。當然也可以不親自去,但是得特殊處理(ref,out,或者指針)。
7.為什么要分體制內和體制外
像java都是體制內的該多好啊。.net 說不,要進行體制改革,讓大量的個頭小的類型變成體制外的,不然的話體制負擔就太重了。
8. 接口是什么
接口是一個邏輯上的概念,是對類型行為的規范和約束。比如歐盟不承認中國的市場經濟地位,他們覺得中國沒有實現“市場經濟地位”的一些規范。
9. 接口的實現
邏輯上的東西只落在頭腦中和紙面上,而在現實中,邏輯上的東西是需要實現來保證的。接口有很多種實現方式。比如,C++中根本沒提供接口的具體實現,那么經常將抽象類當接口用。再比如,haXe的typedef 實際上也是接口的一種實現,它用於編譯時類型檢測。
10. .Net 中接口的實現方案
接口有很多種實現方案,.Net選擇了一種自己認為合理的一種:用引用類型來實現接口。
當然,.Net完全可以把接口實現為值類型,或者值類型與引用類型之外的第三種類型。而.Net既然把它實現為引用類型,我們就得接受這個觀點,受它的約束。
11. 根據10,接口是引用類型,是體制內的類型
12. 從值類型轉換為它所實現的接口類型要裝箱
{
public Int32 Age;
public void SetAge( int age)
{
Age = age;
}
public Int32 GetAge()
{
return Age;
}
}
public interface IPerson
{
void SetAge( int age);
Int32 GetAge();
}
Person p = new Person();
p.SetAge( 5);
Console.WriteLine(p.Age);
IPerson ip = p;
ip.SetAge( 8);
Console.WriteLine(ip.GetAge());
Console.WriteLine(p.GetAge());
上面的代碼輸出結果是:
5
8
5
13. 在特殊情況下,接口是性能殺手
操作100萬個值類型和將100萬個值類型轉換為引用類型再進行操作,性能大不一樣。這時切忌為了抽象為了代碼的優美,而用接口來操作值類型,性能會大幅度下降,GC也會表示壓力很大。我就犯過這個錯誤,將圖像的像素實現某個接口,用接口來操作像素。
14. 白條現象
不要認為某值類型實現了接口,它就“是”接口。實際上,它裝箱后才是接口。這就好比打白條,白條可以轉換為現金,但白條和現款是兩碼事。
而從12的測試結果看,值類型和接口類型是兩碼事。
一個值類型“實現”接口,意思是,這個值類型可以轉換為該接口類型。
15. 繞口令:值類型不是值類型
typeof(ValueType).IsValueType == false
System.ValueType 是引用類型。
16. 值類型隱式繼承自System.ValueType 類型的意思是值類型可以轉換為System.ValueType 類型,轉換過程中有裝箱現象。這也是一種“白條”現象。
在 12 的代碼基礎上進行下列測試:
Person p = new Person();
p.SetAge(5);Console.WriteLine(p.Age);
IPerson ip = p;
ip.SetAge(8);
ValueType vt = p;
Person vtp = (Person)vt;
vtp.SetAge(9);
Console.WriteLine(ip.GetAge());
Console.WriteLine(p.GetAge());Console.WriteLine(vtp.GetAge());
結果是:
5
8
5
9
17. 所有類型隱式繼承自Object類型是他們可以轉換為Object 類型,當然,值類型轉換過程中也有裝箱現象。這也是一種“白條”現象。
Person p = new Person();
p.SetAge(5);
Console.WriteLine(p.Age);
IPerson ip = p;
ip.SetAge(8);
ValueType vt = p;
Object obj = p;
Person vtp = (Person)vt;
Person objp = (Person)p;
vtp.SetAge(9);
objp.SetAge(12);
Console.WriteLine(ip.GetAge());
Console.WriteLine(p.GetAge());
Console.WriteLine(vtp.GetAge());
Console.WriteLine(objp.GetAge());
結果是:
5
8
5
9
12
18. END