c#中的引用類型和值類型

来源:http://www.cnblogs.com/woaixiaozhi/archive/2016/01/09/5116137.html
-Advertisement-
Play Games

一,c#中的值類型和引用類型 眾所周知在c#中有兩種基本類型,它們分別是值類型和引用類型;而每種類型都可以細分為如下類型: 什麼是值類型和引用類型什麼是值類型:進一步研究文檔,你會發現所有的結構都是抽象類型System.ValueType的直接派生類,而System.ValueType本身又是直接....


一,c#中的值類型和引用類型

     眾所周知在c#中有兩種基本類型,它們分別是值類型和引用類型;而每種類型都可以細分為如下類型:

    

  1.  什麼是值類型和引用類型
    • 什麼是值類型:
      • 進一步研究文檔,你會發現所有的結構都是抽象類型System.ValueType的直接派生類,而System.ValueType本身又是直接從System.Object派生的。根據定義所知,所有的值類型都必須從System.ValueType派生,所有的枚舉都從System.Enum抽象類派生,而後者又從System.ValueType派生。  
      •  所有的值類型都是隱式密封的(sealed),目的是防止其他任何類型從值類型進行派生。       
    • 什麼是引用類型:
      • 在c#中所有的類都是引用類型,包括介面。
  2.  區別和性能
    • 區別:
      • 值類型通常被人們稱為輕量級的類型,因為在大多數情況下,值類型的的實例都分配線上程棧中,因此它不受垃圾回收的控制,緩解了托管堆中的壓力,減少了應用程式的垃圾回收的次數,提高性能。
      • 所有的引用類型的實例都分配在托管堆上,c#中new操作符會返回一個記憶體地址指向當前的對象。所以當你在創建個一個引用類型實例的時候,你必須要考慮以下問題:
        • 記憶體是在托管堆上分配的
        • 在分配每一個對象時都會包含一些額外的成員(類型對象指針,同步塊索引),這些成員必須初始化
        • 對象中的其他位元組總是設為零
        • 在分配對象時,可能會進行一次垃圾回收操作(如果托管堆上的記憶體不夠分配一次對象時)
    • 性能:
      • 在設計一個應用程式時,如果都是應用類型,那麼應用程式的性能將顯著下降,因為這會加大托管堆的壓力,增加垃圾回收的次數。
      • 雖然值類型是一個輕量級的類型,但是如果大量的使用值類型的話,也會有損應用程式的性能(例如下麵要講的裝箱和拆箱操作,傳遞實例較大的值類型,或者返回較大的值類型實例)。
      • 由於值類型實例的值是自己本身,而引用類型的實例的值是一個引用,所以如果將一個值類型的變數賦值給另一個值類型的變數,會執行一次逐欄位的複製,將引用類型的變數賦值給另一個引用類型的變數時,只需要複製記憶體地址,所以在對大對象進行賦值時要避免使用值類型。例如下麵的代碼
         1  class SomRef
         2     {
         3         public int x;
         4     }
         5     struct SomeVal {
         6         public int x;
         7     }
         8     class Program {
         9         static void ValueTypeDemo() {
        10             SomRef r1 = new SomRef();//在堆上分配
        11             SomeVal v1 = new SomeVal();//在棧上分配
        12             r1.x = 5;//提領指針
        13             v1.x = 5;//在棧上修改
        14             SomRef r2 = r1;//只複製引用(指針)
        15             SomeVal v2 = v1;//在棧上分配並複製成員
        16         }
        17     }

  3. 常見誤區
    • 引用類型分配在托管堆上,值類型分配線上程棧上:其實這種說法的前半部分是對的,後半部分是錯的。因為變數的值在它聲明的位置存儲的,所以假如某一個引用類型中有一個值類型的變數, 那麼該變數的值總是和該引用類型的對象的其它數據在一起,也就是分配在堆上。(只有局部變數(方法內部聲明的變數)和方法的參數在棧上)
    • 結構是輕量級的類:這種錯誤的信息主要是因為有人認為值類型不應該有方法或者其它有意義的行為-它們應該作為簡單的數據轉移來使用,所以很多人分不清DateTime到底是值類型還是引用類型。
    • 對象在c#中預設的是用過引用傳遞的:其實在調用方法的時候,參數值(對象的一個引用)是以傳值得方式傳遞的,如果你想以引用方式傳遞的話,可以使用ref或者out關鍵字。

二,值類型的裝箱和拆箱操作

1 int i = 5;
2 object o = i;
3 int j = (int)o;
4 Int16 y=(Int16)o;

 

  1.  什麼是裝箱,什麼是拆箱
    • 什麼是裝箱:所謂裝箱就是將值類型轉化為引用類型的過程(例如上面代碼的第2行),在裝箱時,你需要知道編譯器內部都幹了什麼事:
      • 在托管堆中分配好記憶體,分配的記憶體量是值類型的各個欄位需要的記憶體量加上托管堆上所以對象的兩個額外成員(類型對象指針,同步塊索引)需要的記憶體量
      • 值類型的欄位複製到新分配的堆記憶體中
      • 返回對象的地址,這個地址就是這個對象的引用
    • 什麼是裝箱:將已裝箱的值類型實例(此時它已經是引用類型了)轉化成值類型的過程(例如上面代碼的第3行),註意:拆箱不是直接將裝箱過程倒過來,拆箱的代價比裝箱要低的多,拆箱其實就是獲取一個指針的過程。一個已裝箱的實例在拆箱時,編譯器在內部都幹了下麵這些事:
      • 如果包含了“對已裝箱類型的實例引用”的變數為null時,會拋出一個NullReferenceException異常。
      • 如果引用指向的對象不是所期待的值類型的一個已裝箱實例,會拋出一個InvalidCastException異常(例如上面代碼的第4行)。  
  1.  它們在什麼情況下發生,以及如何避免
  2. 1    static void Main(string[] args)
    2         {
    3             int v = 5;
    4             object o = v;
    5             v = 123;
    6             Console.WriteLine(v+","+(int)o);
    7        }

          通過上面的分析我們已經知道了,裝箱和拆箱/複製操作會對應用程式的速度和記憶體消耗產生不利的影響(例如消耗記憶體,增加垃圾回收次數,複製操作),所以我們應該註意編譯器在什麼時候會生成代碼來自動這些操作,並嘗試手寫這些代碼,儘量避免自動生成代碼的情況。

    • 你能一眼從上面的代碼中看出進行了幾次裝箱操作嗎?正取答案是3次。分別進行了哪三次呢,我們來看一下:第一次object o=v;第二次在執行 Console.WriteLine(v+","+(int)o);時將v進行裝箱,然後對o進行拆箱後又裝箱。也就是說裝箱過程總是在我們不經意的時候進行的,所以只有我們充分瞭解了裝箱的內部機制,才能有效的避免裝箱操作,從而提高應用程式的性能。所以對上面的代碼進行如下修改可以減少裝箱次數,從而提高性能:

      1  static void Main(string[] args)
      2         {
      3             int v = 5;
      4             object o = v;
      5             v = 123;
      6             Console.WriteLine(v.ToString() + "," + ((int)o).ToString());//((int)o).ToString()代碼本身沒有任何意義,只為演示裝箱和拆箱操作
      7        }
    • 下麵來討論一下編譯器都會在什麼時候自動生成代碼來完成這些操作
      • 使用非泛型集合時:比如ArrayList,因為這些集合需要的對象都是object,如果你將一個值類型的對象添加到集合中時會執行一次裝箱操作,當你取值時會執行一次拆箱操作,所以在應用程式中應避免使用這種非泛型的集合。
      • 大家都知道System.Object是所有類型的基類,當你調用object類型的非虛方法時會進行裝箱操作(例如GetType方法)。在調用object的虛方法時,如果你的值類型沒有重寫虛方法也要進行裝箱操作,所以在定義自己的值類型時,應重寫object內部的虛方法(例如ToString方式)
      • 將值類型轉化為介面類型時也會進行裝箱操作,這是因為介面類型必須包含對堆上的一個對象的引用。

三,泛型的出現(本節只簡單介紹泛型對裝箱和拆箱所起的作用,關於泛型的具體細節請參考下一篇文章)

    •  什麼泛型
      • 泛型是CLR和編程語言提供的一種特殊機制,它在c#2中才被提供出來。
    •  它對避免裝箱有什麼作用?
      • 在使用泛型時需要指定要裝配的類型,這樣可以減少裝箱操作,比如下麵的代碼
         1   static void Main(string[] args)
         2         {
         3             ArrayList dateList = new ArrayList { 
         4             DateTime.Now
         5             };
         6 
         7             IList<DateTime> dateT = new List<DateTime> { 
         8             DateTime.Now
         9             };
        10         }

        使用ArrayList時,每添加一個時間都會進行一次裝箱操作,而使用List<DateTime>時就不會進行裝箱操作,從而提高應用程式的性能。

    •  C#中常見的泛型集合:

      Queue<T>;

      Stack<T>;

      List<T>;

      Dictionary<Tkey,Tvalue>;

      HashSet<T>;

       

       在使用這些集合之前我們必須要理解每一種集合的工作原理(沒事自己可以實現一下),瞭解每一種集合的適合場合,這樣才能寫出高效的代碼。

四,在設計時如何選擇類和結構體

在面試的時候,我們經常被問的一個問題(還有另外一個問題,如何選擇抽象類和介面,下次我會單獨聊聊這個問題),下麵我們來聊聊在設計時應該如何選擇結構體和類

    •  什麼是結構體
      • 結構體是一種特殊的值類型,所以它擁有值類型所以的特權(實例一般分配線上程棧上)和限制(不能被派生,所以沒有 abstract 和 sealed,未裝箱的實例不能進行線程同步的訪問)。
    •  什麼情況下選擇結構體,什麼情況下選擇類
      • 在大多數的情況下,都應該選擇類,除非滿足以下情況,才考慮選擇結構體:
      • 類型具有基元類型的行為
      • 類型不需要從其它任何類型繼承
      • 類型也不會派生出任何其它類型
      • 類型的實例較小(約為16位元組或者更小)
      • 類型的實例較大,但是不作為方法的參數傳遞,也不作為方法的返回值。

都說程式是一門註重實踐的學科,但是也只有熟悉理解了這些概論的東西,才能在實踐時寫出優秀的代碼,有不對或者不合理的地方歡迎在下麵討論;

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 博客地址:http://www.cnblogs.com/oumyye/問題一:==與equal的區別?==和 equals 都是比較的,而前者是運算符,後者則是一個方法,基本數據類型和引用數據類型都可以使用運算符==,而只有引用類型數據才可以使用 equals,下麵具體介紹一下兩者的用法以及區別.=...
  • 首先非常感謝11期的學長薜保庫提供了一種非常實用函數遞歸方法,讓實現三層菜單如此簡單,不過對所遍歷的嵌套字典或列表格式有所要求。有特定的環境下非常實用。 主要針對中國的各省市區進行展示,採用了百度的js介面: http://passport.baidu.com/js/site...
  • 1、業務模塊與數據模塊分離在實際開發中,我們項目的架構業務模塊和數據模塊是分離的,舉個例子,假設我們的項目有"人員管理模塊"和"酒店管理模塊"兩個模塊,按照上一章的介紹,我們會建立下圖所示的項目結構:其中,人員管理模塊的controller、service、dao、mapper都在一個項目中,而在實...
  • 題目:取一個整數a從右端開始的4~7位。程式分析:可以這樣考慮:(1)先使a右移4位。(2)設置一個低4位全為1,其餘全為0的數。可用~(~0>4;c=~(~0<<4);d=b&c;printf("%o\n%o\n",a,d);}
  • haha,貪心,邊界條件折騰了我一會兒 1 #include 2 #include 3 #include 4 #include 5 using namespace std ; 6 7 const int MAXN = 1000000 + 200 ; 8 int N , K ; 9 int dis ....
  • 轉自:http://www.haorooms.com/post/gem_not_use最近在安裝SASS的時候,用到gem命令,但是運行出行如下錯誤!C:\Users\len>gem install sassERROR: While executing gem ... (Gem::RemoteFe....
  • using (Ajax.BeginForm("GetBasicInformation", "Employee", //new AjaxOptions { UpdateTargetId = "basicInfo", //設置HTML元素的ID,從伺服器接收的內容將被插入到該元素中LoadingElem...
  • live() 與bind()作用基本一樣。 最重要區別:live()可以將事件綁定到當前和將來的元素(eg:為id=zy元素綁定點擊事件,而當你用js動態生成一個節點並插入到dom文檔結構中時,如果你是用bind()綁定的,怎麼新插入的節點將不會有該bind綁定事件。而live()則可以); ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...