【C#進階系列】11 泛型

来源:http://www.cnblogs.com/vvjiang/archive/2016/03/18/5290159.html
-Advertisement-
Play Games

泛型是CLR和編程語言提供的一種特殊機制,它用於滿足“演算法重用” 。 可以想象一下一個只有操作的參數的數據類型不同的策略模式,完全可以用泛型來化為一個函數。 以下是它的優勢: 這就是為什麼List<T>淘汰了ArrayList的原因,特別是在進行值類型操作時,因為裝箱拆箱過多而差距很大。 約定:泛型


泛型是CLR和編程語言提供的一種特殊機制,它用於滿足“演算法重用”  。

可以想象一下一個只有操作的參數的數據類型不同的策略模式,完全可以用泛型來化為一個函數。

以下是它的優勢:

  • 類型安全
    • 給泛型演算法應用一個具體的數據類型時,如果不相容這種類型,就會編譯錯誤或者報異常。
  • 更清晰的代碼
    • 減少了強制轉換,讓代碼更簡潔
  • 更佳的性能
    • 用泛型可以有效避免裝箱拆箱的操作,且無需在進行強制轉換時驗證是否類型安全,這兩點都有效提高了代碼的性能。

這就是為什麼List<T>淘汰了ArrayList的原因,特別是在進行值類型操作時,因為裝箱拆箱過多而差距很大。

約定:泛型參數要麼為T要麼以大寫T開頭,例如List<T>。

FCL中的泛型

System.Collections.Generic和System.Collections.ObjectModel命名空間中提供了多個泛型集合類和介面。

System.Collections.Concurrent命名空間則提供線程安全的泛型集合類。

System.Array類則提供了大量的靜態泛型方法。

泛型的基礎結構

.net 2.0才有泛型。

  • 開放類型和封閉類型
    • 之前我們講到CLR會為各種類型創建類型對象,同樣一個新的泛型類TroyList<T>也會創建一個類型對象,我們將具有泛型參數的類型稱為開放類型。
      • 不能構造開放類型的實例
    • 而指定了泛型類型實參的泛型類型稱為封閉類型,例如:TroyList<int>。
      • 可以構造封閉類型的實例
      • 如果TroyList<T>定義了靜態欄位或者方法,那麼TroyList<int>和TroyList<string>之間並不共用,因為這其實是兩個不同的類型對象。
  • 泛型類型的繼承
    • 使用泛型類型並指定類型實參後,實際上是一個新的封閉類型,新的類型對象從泛型類型派生自的那個類型派生。即List<T>派生自Object,那麼List<int>就派生自Object。
  • 關於代碼爆炸的優化
    • 看到這裡你可能想到了,一個開放類型實際上會有多個封閉類型,比如一個List<T>會有List<int>,List<string>等N多封閉類型。實際上就是N多的類型對象,生成N多的重覆代碼,於是這被稱作代碼爆炸。
    • 關於優化:
      • 兩個不同的程式集用到同一種封閉類型,只會由JIT編譯器變異一次
      • CLR認為所有用引用類型做類型實參的封閉類型完全相同,所以代碼可以共用。也就是說List<String>和List<Stream>的方法編譯後的代碼可以通用。因為操作的不同的引用類型的地址大小都是一樣的。

委托和介面的逆變和協變泛型類型實參

泛型委托和介面的每個泛型類型參數都可標記為協變數和逆變數,利用此功能可實現相同類型但實參類型不同的委托和介面的相互轉換。(很繞,不明白可以看下麵)

  • 不變數
    • 意味著泛型類型參數不可更改
  • 逆變數
    • 意味著泛型類型參數可以從一個類更改為它的派生類。用in標記,逆變數泛型類型參數只能出現在輸入位置。
  • 協變數
    • 意味著泛型類型參數可以從一個類更改為它的基類。用out標記,協變數泛型類型參數只能出現在輸出位置。

舉個例子

public class 基類 { }
    public class 派生類 : 基類 { }
    public class Test{
        public delegate TResult MyFunc<in T1, out TResult, T2>(T1 a, T2 b);//第一個為逆變數,第二個為協變數,第三個為不變數

        void show() {
            MyFunc<基類, 基類, 基類> fn1 = null;
            //以下註釋為我自己的理解方式,只是為了方便理解而已
            MyFunc<派生類, 基類, 基類> fn2 = fn1;//MyFunc<派生類, 派生類, 基類> fn2 = fn1;轉換錯誤
            MyFunc<基類, Object, 基類> fn3 = fn1;//MyFunc<Object, Object, 基類> fn3 = fn1;轉換錯誤
            MyFunc<派生類, Object, 基類> fn4 = fn1;
        }
    }

依然很繞,實際上不懂也沒關係,轉換不了編譯器自然會提示。瞭解有這個東西就行了,也建議用int和out指定泛型委托的類型變數。更多的時候我們會用自帶的泛型委托Action和Func,這兩個泛型委托的參數都用到in和out。

關於泛型方法的類型推斷

 void Go() {
            String s1 = "213";
            Object s2 = "123";
            Show(s1, s2);//不指定Show<T>的T的玩法就叫類型推斷,類型推斷通過傳入的變數s1和變數s2的變數類型來推斷,而不是實際類型。因為這裡兩個變數類型不同,所以函數編譯不通過。
        }
        void Show<T>(T a,T b) {
            
        }

約束

泛型的約束是一個很有意思的事情。

void Show<T>(T a,T b) where T :IList { }

比如上面這個函數,約束傳入的類型T必須實現了IList介面。

通過約束可以限制傳入的類型,然而正式因為提供了這層約束,保證了傳入的類型都實現了IList介面,我們就可以使用IList的各種方法了。

約束分類:

  • 主要約束
    • 主要約束可以是代表非密封類的一個引用類型。(可以指定0到1個主要約束)
    • 兩個特殊的主要約束為class和struct,分別約束傳入的參數為引用類型和值類型。(特例的特例,struct不能約束Nullable<T>)
    • 約束不能指定以下特殊引用類型:Object,Array,Delegate,MulticastDelegate,ValueType,Enum或者Void。
  • 次要約束
    • 次要約束代表介面類型。(可以指定0到多個次要約束)
    • 特殊的次要約束,即指定的兩個泛型類型參數中,一個繼承另一個,例如:where T2:T1。
  • 構造器約束
    • 構造器約束約束類型實參,一定是實現了公共無參構造函數的非抽象類型。(可以指定0到1個構造器約束)
    • 所有值類型都隱式提供了公共無參構造器。所以同時使用struct和new()約束被認為是多餘的,會報錯。

可驗證性

以下幾種情況因為代碼不可驗證是否合法,所以將報錯:

  • 泛型類型變數的轉換
    • 原因:不可將泛型類型T的變數轉換為其它類型,因為T可能為任何變數,所以可能轉換失敗
    • void Show<T>(T obj){
        string a=(string)obj;   //出錯  
      }
    • 解決方案:
      void Show<T>(T obj)
      {
          string a = obj as string;//對於string而言,其實這裡用ToString方法可能更恰當一點
      }

      值類型可以先強制轉換為object,再轉為具體的值類型。然而我認為這樣的代碼還是需要開箱裝箱的,也許可以考慮修改下演算法。

  • 將泛型類型變數設為預設值
    • 原因:因為T可以是值類型和引用類型,所以不可能設置一個值類型或者引用類型的預設值
    • 解決方案:可以考慮加約束或者用default(T),作為預設值。
  • 兩個泛型類型變數相互比較
    • 原因:因為非基元類型的值類型除非重載了==操作符,否則會報錯。
    • 解決方案:可以考慮約束為class或者用Equals。(註意哦,有可能因為Equals的被覆蓋所以具體不確定是判斷同一性還是相等性)
  • 泛型類型變數作為操作數使用
    • 原因:因為非基元類型的值類型除非重載了操作符,否則會報錯。
    • 解決方案:反射,操作符重載或者dynamic。(會有性能影響,我一般用dynamic了)

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

-Advertisement-
Play Games
更多相關文章
  • 1.在要配置互信的機器(web-1和web-2)上生成各自經過認證的key文件。 2.將所有的key文件彙總到一個總的認證文件夾中。 3.將打包的key發給想要進行互信的機器(web-1,web-2)
  • 開始學習hadoop啦!!! 在Ubuntu14.04上新建了一個名為hadoop的用戶,但總是遇到各種許可權問題,於是就想乾脆把這個賬戶變成root賬戶。 網上查到說是直接修改/etc/sudoers,修改時提示read-only,修改不了,自然而然想到去修改文件的許可權, sudo chown u+
  • 三、gdb調試(下)01.查看運行時數據print - 查看變數值ptype - 查看類型print array - 查看數組print *array@len - 查看動態記憶體print x =5 - 改變運行時數據#vi simple.c#include<stdio.h>long fun(int
  • 系統來自系統媽:http://www.xitongma.com 電腦公司最新GHOST win7系統32位優化精簡版V2016年3月 系統概述 電腦公司ghost win7 x86(32位)萬能裝機版集成的軟體符合電腦公司及電腦城裝機絕大多數人要求及喜好,既大眾,又時尚,人人喜歡,處處適用。自動判斷
  • asp.net 編譯錯誤類型“同時存在於”不同的dll中. 出現這種錯誤大概有三種情況: 1、ASPX頁面,一個*.ASPX,對應著一個*.cs文件,兩者其實是一個文件,通過兩者實現代碼分離,每個*.aspx頁面都引用著自身的CS文件:如果兩個頁面引用了相同得.CS文件,在發佈得時候也會出現這種錯誤
  • 普通電腦沒有通用的輸入輸出口(GPIO),但有時候我就想輸入一個開關量。 比如讓用戶拉一下拉繩開關就啟動某個應用,比如裝一個觸點開關判斷門是打開的還是關閉的,比如.... 需求是如此簡單,你都不願意花幾十塊錢去買一個單片機,更不用說PCI擴展卡、PLC之類的了。。怎麼辦吶? 有辦法!最簡單的用串口就
  • 運行-cmd,輸入下麵命令:C:\WINDOWS\Microsoft.NET\Framework\v版本號\aspnet_regiis.exe -i即可 以下是aspnet_regiis.exe參數的說明信息: -i - 安裝 ASP.NET 的此版本,並更新 IIS 元資料庫根處的腳本映射和根以下
  • 使用ASP.NET模版生成HTML靜態頁面並不是難事,主要是使各個靜態頁面間的關聯和鏈接如何保持完整。本文介紹了使用ASP.NET模版生成HTML靜態頁面的五種方案。 ASP.NET模版生成HTML靜態頁面方案1: 你可以用這個函數獲取網頁的客戶端的html代碼,然後保存到.html文件里就可以了。
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...