值類型不是值類型(ValueType is NOT a Value Type):閑談.Net類型


 .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  struct Person : IPerson
{
     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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM