C#----值類型與引用類型


     要了解一門編程語言,首先就要了解它的類型。我們知道,C#一共分為兩大類型:值類型和引用類型,但值類型並不單純是我們java中的基本數據類型那么簡單,有關於是否使用值類型還是個值得討論的問題:因為裝箱機制。C#的值類型還能夠自定義方法,甚至能夠實現引用類型的接口類型!這已經超出了我的想象范圍了!

    先來點基礎的東西:

基本內容.

     文檔是我們學習的好幫手,在C#的文檔中,我們必須注意,凡是引用類型的,名字都是"xx類",凡是值類型的,就叫"xx結構"或"xx枚舉"。

     很多時候,我們的初始化操作的右值是表達式。如果左值是值類型,那么它的值就是表達式的值,但如果是引用類型,則是一個引用,並不是該引用指向的對象(學過java,所以對這現象非常熟悉),所以,在C#中,String.Empty的值不是一個空字符串,而是對空字符串的引用。

     聲明一個變量,除了它的類型信息外,最重要的就是值的存儲地點。變量的值一般是在它聲明時的位置存儲的,而實例變量的值存儲在實例本身存儲的地方,引用類型和靜態變量則存儲在堆中。

     也許我們會以為,值類型一定在棧(stack,有些地方叫線程棧)上,引用類型一定在堆(heap,有些地方叫托管堆)上,其實這是錯誤的,值類型也可以在堆上,像是前面講過的,變量的值存儲在它聲明的地方,如果我的值類型是在一個引用類型中聲明的,那么該值類型就是在堆上。方法參數一定是在棧(方法是用棧幀存儲它的信息)上,但局部變量不一定,它也可以是在堆上(讓我們想想匿名方法,如果它捕獲了一個外部變量,該變量就會作為隱藏委托類型而保存在堆上)。好了,一個潛在的想法出現了:如果一個引用類型保存在值類型中,像是結構中,會怎么樣呢?引用類型的數據依然保存在堆中,但它的引用保存在棧上,因為值類型也只擁有它的引用而已。

     值類型不能派生出其他類型,因為它是隱式密封(sealed)的,所以它並沒有引用類型實例對象開頭的額外信息(用於標識對象的實際類型和其他信息),即類型對象指針和同步塊索引。這些額外信息並不能修改,所以我們永遠也不能修改對象的類型,但我們可以轉換為其他類型。執行強制類型轉換時,我們會獲取一個引用,該引用會檢查它引用的對象本身是否是該目標類型的有效對象,若是,返回該引用並賦給目標類型,否則拋出異常。引用並不清楚它實際引用的對象的類型,所以我們的引用可以指向其他類型。

    基礎的東西講完后,就應該講講一些不一樣的東西(雖然大部分很多人都已經非常熟悉了)。細節不討論,只是單單從幾個話題出發並做一下延伸。

話題1:結構是輕量級的類

      結構雖然是值類型,但它可以定義屬性和方法,類的行為它都具備,加上它是值類型,比起引用類型來說,對內存更加友好。所以,就有一種說法:結構是輕量級的類。這種說法見仁見智,值類型的確是"輕":不需要垃圾回收(不在堆上分配),不會因類型標識而產生開銷(沒有類型對象指針和同步塊索引),而且不需要取值這一步操作(引用類型的字段一般都是私有的,我們只能通過訪問器getter取得其值)。但引用類型也有它的好處:在執行傳參,賦值,返回值等類似操作時,只需要賦值4或8字節(具體得看CLR),因為我們傳遞的只是一個引用(我想說指針,但又覺得不合適,從來就沒有任何一種說法認為引用就等同於指針,雖然CLR的引用的確是一個地址,但CLR強調,它們是引用),而不是復制所有數據。這點在容器那里非常好用,像是List,如果容量很大,那這個傳參動作就太可怕了。

    值類型也並不總是對內存友好,因為隱式的裝箱機制,會使某些情況像是循環等,使用值類型會造成可怕的負擔。C#中的值類型使用起來並不像java的基本數據類型那么簡單(java並沒有隱式的裝箱機制,基本數據類型不可能直接轉換為類),除非是以下幾種情況,我們才能放心使用值類型:

    1.值類型是不可變(immutable)的,即該類型沒有提供任何方法來修改它的字段。要做到這點,我們必須將值類型的所有字段都設置為readonly(只讀)。這主要針對結構這種封裝其他值類型的值類型。

   2.類型的實例較小(小於16字節),因為按值傳遞需要復制字段,但類型實例較大以致不可能作為實參傳遞,也不可能作為返回值的話,也可以考慮使用值類型。

   就算滿足上面的條件,我們也必須考慮到值類型的缺點(裝箱機制不在列表中,因為這個話題前面已多次提醒了):

   1.值類型繼承自System.ValueType,該類除了提供與System.Object一樣的方法外,還做了一個動作:覆寫了Equals()方法和GetHashCode()方法(值類型的比較需要考慮到它的字段,但默認的比較是引用),而默認的實現存在性能問題。我們不能苛求設計者能夠考慮到所有情況,所以,大部分情況都要我們自己覆寫這兩個方法(這個問題不知道是不是從java而來,java也存在這樣的問題)。

   2.值類型可以有自己的方法,但它不能派生也不能繼承(雖然能實現接口),因此它不能含有虛方法,所有方法也是不可覆寫的。

   3.引用類型的默認值是null,但我們引用一個null的引用類型時會拋出異常:NullReferenceException,但值類型默認值是0,並不會拋出異常。CLR為了彌補這點,提供了可空性(nullability)標識---可空類型(nullable)。

   4.值類型之間的相互賦值,會導致字段的復制,但引用類型只是復制引用。

  5.值類型並不在堆上分配,所以當它被銷毀時,不會通過Finalize方法接到一個通知(這點在有些地方很重要,這時就需要裝箱)。

  看了以上的討論,相信對使用值類型是有點怕怕的:自己是否用錯了呢?程序員是不需要顧慮那么多的,寫代碼最主要是能夠表達清楚自己的意圖,至於性能這方面,是可以在后期進行重構和優化的。

話題2:對象在C#中默認是通過引用傳遞

     引用傳遞(pass by reference)的定義非常復雜,百度百科的解釋是這樣的:可以將一個變量通過引用傳遞給函數,這樣該函數就可以修改其參數的值,而引用的解釋就是某一變量的一個別名,對引用的操作與對變量直接操作完全一樣(很多人都說java是按引用傳遞,其實這種說法是不嚴謹的,嚴格意義上是傳遞引用對象地址值的按值傳遞)。如果我們以按引用傳遞的方式傳遞變量,那么調用的方法可以通過更改其參數值,來改變調用者的變量值。但C#中引用類型變量的值雖然叫引用,但不是對象本身,它更接近於指針。

      就算是傳參,有些情況也不能修改它的值:

class Program
    {
        public static void Main(String[] args)
        {
            String builder = "hello";
            Show(builder);
            Console.WriteLine(builder);
        }

        static void Show(String str)
        {
            str = "word";
            Console.WriteLine(str);
        }
    }

       在Show()方法里,修改的只是builder的一個副本。當然,String雖然是引用類型,但它是不可變的。我們來傳一個真正的引用類型:

 class Program
    {
        public static void Main(String[] args)
        {
            People people = new People();
            Show(people);
            Console.WriteLine(people.name);
        }

        static void Show(People people)
        {
            people.name = "男人";
        }
    }

    public class People
    {
        public String name = "";
    }

        這里我們看到,字段name改變了。

       很困惑吧?為什么還說C#是按值傳遞呢?C#中,引用類型作為方法參數確實是按"值"傳遞,因為引用類型的值是引用,而該引用是一個地址值,相當於指針(只是相當於,並不等於)。真正的引用傳遞的就是對象本身,因為引用本身就是對象的別名,但C#是不會傳遞對象本身的。

      這個問題非常讓人糾結,尤其是CLR采取了引用這個說法,使得我們更加困擾了。

       C#的值類型非常奇怪,我們甚至可以用new來聲明:

int number = new int();
number = 5;

      這並沒有錯,但剛從java中跳出來的我非常驚訝!

     C#編譯器是很聰明的,它知道number是一個值類型,因為它並沒有類型對象指針,於是在棧上為它分配內存,然后確保所有字段都初始化為0。這樣的動作就算不用new也行:

int number;

    但是用new,編譯器就認為該實例已經初始化了,而上面的情況如果我們為它賦值就會發生錯誤。所以,聲明一個值類型最好就是為它進行初始化,哪怕只是默認值。

    關於這方面的討論,很多時候我都有心無力,畢竟自己這個初學者要想啃下CLR,難度很大,有什么不對的地方還請見諒。

    

   

 

 

 

 

 

 

   


免責聲明!

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



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