類型參數化-泛型

来源:http://www.cnblogs.com/Helius/archive/2016/04/23/5425164.html
-Advertisement-
Play Games

中間因為比較忙,空了那麼多天,都感覺有點罪過了。話不多說,這一篇主要是要講C#2.0提出的一個新特性,那就是泛型。(現在都C#6.0了。囧囧) 1、什麼是泛型? C#1.0中的委托特性使方法可作為其他方法的參數來傳遞,而C#2.0中提出的泛型特性則使類型可以被參數化,從而不必再為不同的類型提供特殊版 ...


中間因為比較忙,空了那麼多天,都感覺有點罪過了。話不多說,這一篇主要是要講C#2.0提出的一個新特性,那就是泛型。(現在都C#6.0了。囧囧)

1、什麼是泛型?

C#1.0中的委托特性使方法可作為其他方法的參數來傳遞,而C#2.0中提出的泛型特性則使類型可以被參數化,從而不必再為不同的類型提供特殊版本的方法實現。從字面上來的意思來講,泛型代表的就是“通用類型”,它可以代替任意的數據類型,使類型參數化,從而達到只實現一個方法就可以操作多種數據類型的目的,將實現行為與方法操作的數據類型分離,實現了代碼的重用。下麵通過.NET類庫中的泛型來說明到底什麼是泛型。

clas program

{

  static void Main(string[] args)

  {

    //用int作為實際參數來初始化泛型類型

    List<int> intList=new List<int>();

    //從int列表添加元素3

    intList.Add(3);

    

    //用string作為實際參數來初始化泛型類型

    List<string> stringList=new List<string>();

    //從string列表添加元素

    stringList.Add("learninghard");

  }

}

在以上代碼中,List<T>是.NET類庫中實現的泛型參數,T是泛型參數,可以理解為形參。如果想實例化一個泛型類型,必須傳入實際的類型參數,如上面代碼中的int和string,就是實際的額類型參數。同時你也可以自己實現泛型類型,下麵會介紹泛型類型的實現方法。

 

2、C# 2.0為什麼要引入泛型?

這個就需要用代碼來解釋了。

public class Compare

{

  //比較兩個整數大小的方法,方法返回較大的那個整數

  public static int compareInt(int i1,int i2)

  {

    if(i1.CompareTo(i2)>0)

    {

      return i1;

    }

    else

    {

      return i2;

    }

  }

}

以上的代碼實現對於int類型數據來說完全是沒有問題的,但是當客戶說我想實現兩個字元串大小的比較,你就必須再添加一個比較字元串的大小了。如果客戶說還想添加一個比較浮點數的大小,那你肯定又只能添加一個方法啦。顯然,這種方式是一種比較low的方式,其實方法的代碼都非常的相似,所以我們完全可以只定義一個比較方法就能比較所有不同類型的大小。

這時,泛型就派上用場了,就是為這種情況而生,所以,微軟就在C#2.0中提出了泛型的特性。

//Compare<T>為泛型類,T為類型參數

public class Compare<T> where T:IComparable

{

  //使用泛型實現的比較方法

  public static T compareGeneric(T t1,T t2)

  {

    if(t1.CompareTo(t2)>0)

    {

      return t1;

    }

    else

    {

      return t2;

    }

  }

}

在以上的代碼中,我們實現了一個自定義的泛型類,其中T是泛型的類型參數,compareGeneric是實現的泛型方法,代碼中的where語句是類型參數的約束,它用來使類型參數可以適用於CompareTo方法。有了泛型之後,就不需要針對每種數據類型重覆實現相似的比較方法了。

class Program

{

  static void Main(string[] args)

  {

    //調用泛型方法

    Console.WriteLine(Compare<int>.compareGeneric(3,4));

    Console.WriteLine(Compare<string>.compareGeneric("abc","a"));

    Console.ReadKey();

  }

}

泛型除了可以實現代碼重用外,還提供了更好的性能和類型安全特性。

 

3、全面解析泛型

3.1 類型參數

在前面的泛型代碼中,就是類型參數。無論調用類型方法還是初始化泛型實例,都需要用真實類型來代替T,可以把T理解為類型的一個占位符,即告訴編譯器,在調用泛型時必須為其指定一個實際類型。

根據泛型類型參數是否提供實際類型,又可把泛型分為兩類:未綁定的泛型和已構造的泛型。如果沒有為類型參數提供實際類型,此時的泛型稱為未綁定的泛型;而如果已指定了實際類型作為參數,則此時的泛型稱為已構造的泛型。

已構造泛型又可分為開放類型和密封類型。其中,開放類型是指包含類型參數的泛型,所有未綁定的泛型類型都屬於開放類型;而封閉類型則是指那些已經為每一個類型參數都傳遞了實際數據類型的泛型。

用以下代碼可以判斷泛型類型是開放還是封閉的。

public class DictionaryStringKey<T>:Dictionary<string,T>{}

class Program

{

  static void Main(string[] args)

  {

    //Dictionary<,>是一個開放類型,它有兩個類型參數

    Type t=typeof(Dictionary<,>);

    Console.WriteLine("是否為開放類型:"+t.ContainsGenericParameters);

    //DictionaryStringKey<>也是一個開放類型,但它只有一個類型參數

    t=typeof(DictionaryStringKey<>);

    Console.WriteLine("是否為開放類型:"+t.ContainGenericParameters);

    //DictionaryStringKey<int>

    t=typeof(DictionaryStringKey<int>);

    Console.WriteLine("是否為開放類型:"+t.ContainGnericParameters);

  }

}

 

3.2 泛型中的靜態欄位和靜態函數問題

每個封閉的泛型類型中都有僅屬於它自己的靜態數據。

//泛型類型,具有一個類型參數

public static class TypeWithStaticField<T>

{

  //靜態欄位

  public static string field;

  //靜態構造函數

  public static void OutField()

  {

    Console.WirteLine(field+":"+typeof(T).Name);

  }

}

//非泛型類

public static class NoGenericTypeWithStaticField

{

  public static string field;

  public static void OutField()

  {

    Console.WriteLine(field);

  }

}

class Program

{

  static void Main(string[] args)

  {

    //使用不同類型實參來實例化泛型實例

    TypeWithStaticField<int>.field="一";

    TypeWithStaticField<string>.field="一";

    TypeWithStaticField<Guid>.field="一";

 

    //對於非泛型類型,此時field只會一個值,每次賦值都改變了原來的值

    NoGenericTypeWithStaticField.field="非泛型類靜態欄位一";

    NoGenericTypeWithStaticField.field="非泛型類靜態欄位二";

    NoGenericTypeWithStaticField.field="非泛型類靜態欄位三";

 

    NoGenericTypeWithStaticField.OutField();

    TypeWithStaticField<int>.OutField();

    TypeWithStaticField<string>.OutField();

    TypeWithStaticField<Guid>.OutField();

    Console.ReadKey();

  }

}

結果如下:

從圖中結果可以看出,每個封閉的泛型類型都有屬於它的靜態欄位。這是因為,在使用實際類型參數代替泛型參數時,編譯器會根據不同的類型實參,重新生成類型。

對於編譯器來說,每個封閉泛型類型都是一個不一樣的類型,所以它們都有屬於它自己的靜態欄位。靜態構造函數也一樣,每個封閉的泛型類型都有一個靜態構造函數。

 

3.3 類型參數的推斷

class Program

{

  static void Main(string[] args)

  {

    int n1=1;

    int n2=2;

    genericMethod(ref n1,ref n2);

    Console.WriteLine("n1的值現在為:"+n1);

    Console.WriteLine("n2的值現在為:"+n2);

  }

 

  //泛型方法

  private static void genericMethod<T>(ref T t1,ref T t2)

  {

    T temp=t1;

    t1=t2;

    t2=temp;

  }

}

在以上的代碼中,編譯器會根據傳遞的方法實參來判斷傳入的實際類型參數。如果編譯器根據傳入的參數不能推斷出實際參數類型,就會出現編譯時錯誤。

註意:類型推斷只能用於泛型方法,它對泛型類則並不適用,因為編譯器不能通過泛型類的構造函數推斷出實際的類型參數。

 

3.4 類型參數的約束

不知大家是否註意過,前面的代碼中曾經出現過where T:IComparable的代碼,這個where語句用來使類型參數繼承於IComparable介面,從而對類型參數進行約束。

前面的比大小中,如果沒有進行約束,代碼編譯的時候就會出現錯誤,編譯通不過,所以,對類型參數進行約束是必須的。

C#中有4種約束可以使用,它們的語法類似:約束要放在泛型方法或類型聲明的末尾,並且要使用where關鍵字。

(1)引用類型約束

表現形式為T:class,它確保傳遞的類型實參必須是引用類型。

註意,約束的類型參數和類型本身沒有關係,即在定義一個泛型結構體時,泛型類型一樣可以被約束為引用類型。其中有幾個特殊的引用類型是不能用的:System.Array、System.Delegate、System.MulticastDelegate、System.ValueType、System.Enum、System.Void。

下麵代碼定義的泛型類:

public class samplehere<T> where T:Stream

{

  public void Test(T stream)

  {

    strem.Close();

  }

}

從以上的代碼中,類型參數T設置了引用類型約束。where T :Stream的意思是告訴編譯器:傳入的類型實參必須是System.IO.System,或者是從Stream派生的一個類型。

如果一個類型參數沒有指定約束,則預設T為System.Object類型。但若你在代碼中顯式地指定了System.Object約束,則編譯器會報錯。

(2)值類型約束

值類型約束的表示形式為T:Struct,它確保傳遞的類型實參是值類型,包括枚舉。但這裡的值類型不包括可空類型。

public class samplevaluetype<T>:struct

{

  public static T Test()

  {

    return new T();

  }

}

在上面的代碼中,new T()是可以通過編譯的,因為T是一個值類型,而所有值類型都有一個公共的無參構造函數。但如果不對T進行約束,或約束為引用類型,上面的代碼就會報錯。

(3)構造函數類型約束

構造函數類型約束的表示形似為T:new(),如果類型參數有多個約束,則此約束必須最後指定。構造函數類型約束確保指定的類型參數有一個公共無參構造函數的非抽象類型。

這裡需要註意一點:如果同時指定構造器約束和struct約束,C#編譯器會認為這是一個錯誤。因為這樣的指定是多餘的,所有值類型都隱式地提供了一個無參公共構造函數。

(4)轉換類型約束

轉換類型約束的表示形式為T:基類名、T:介面名或T:U。T:基類名確保指定的類型實參必須是基類或派生自基類的子類;T:介面名確保指定的類型實參必須是介面或實現了該介面的類;而T:U則確保T提供的類型實參必須是U提供的類型實參或派生於U提供的類型實參,即前面一個類型實參必須是後面的類型實參或後面類型的實參子類。

(5)組合約束

組合約束是將多個不同種類的約束合併在一起的情況。這裡就需要註意,沒有任何一種類型既是引用類型,又是值類型,所以引用約束和值約束不能同時使用。如果存在多個轉換類型約束,其其中一個是類,則必須放在介面的前面。不同類型參數可以有不同的約束,但每種類型參數必須分別使用一個單獨的where關鍵字。

class Sample<T> where T:class,Disposable,new();

class Sample<T,U> where T:class where U:struct 

 

總結:至此,泛型已經講完了,應該有一個全面的認識了。不過還是要多敲代碼,不斷實踐,才能提高。


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

-Advertisement-
Play Games
更多相關文章
  • C#泛型是一種高復用性、安全和高效的技術,通過類型參數可以將參數的聲明、實現推遲到客戶代碼中。但是這種延遲卻降低了類型參數在泛型定義中的可操作性。例如資源釋放。 但是如果T實現了IDisposable介面,則上面代碼可能存在資源泄露的風險。但是由於不知道T是否實現了IDisposable介面,所以不 ...
  • 菜鳥要飛系列目錄 1.(菜鳥要飛系列)一,基於Asp.Net MVC5的後臺管理系統(前言) 2.(菜鳥要飛系列)二,基於Asp.Net MVC5的後臺管理系統(實現登陸功能) 3.(菜鳥要飛系列)三,基於Asp.Net MVC5的後臺管理系統(用戶的增刪改查功能) 4.(菜鳥要飛系列)四,基於As ...
  • 最近由於伺服器變更為Linux系統.MsSql for Linux什麼時候出來到生產環境使用還是要很長時間的.於是考慮使用Mysql資料庫,ORM使用EF.於是先踩下坑順便記錄一下,有需要的tx可以參考下.當你考慮使用EF連接Mysql的時候肯定是已經在網上搜了一堆教程.網上教程基本都是使用控制台做 ...
  • 現在在Windows下的應用程式開發,VS.Net占據了絕大多數的份額。因此很多以前搞VC++開發的人都轉向用更強大的VS.Net。在這種情況下,有很多開發人員就面臨瞭如何在C#中使用C++開發好的類的問題。下麵就用一個完整的實例來詳細說明怎樣用托管C++封裝一個C++類以提供給C#使用。 比如,現 ...
  • EF6開始提供了通過async和await關鍵字實現非同步查詢和保存的支持(.net 4.5及更高版本)。雖然不是所有的操作都能從非同步中獲益,但是耗時的操作、網路或IO密集型任務中,使用非同步可以提升客戶端性能和增強伺服器的擴展性。 本文將覆蓋一下主題: 實例演練非同步操作 創建模型 創建同步程式 改為異 ...
  • 所有需要賬戶登錄的website 基本都會想到這樣一個問題, 如何保持用戶在一定時間內登錄有效。 最近本人就在項目中遇到這樣的需求,某些頁面只能Admin賬戶登錄後訪問, 當登錄Admin賬戶後如何才能保持登錄信息呢? 用Cookie或者Session來保存登錄信息已經是一種比較成熟的技術。但是對於 ...
  • 1.這裡僅對web控制項而言,onclick事件執行的是客戶端中的代碼, 可以把事件寫在html頁面上,也可以放在調用的js文件中(此處為A.js)。 A.js: 運行結果: 2.onserverclick事件,這個是執行服務端的方法。 對應的在後臺補充相應的事件: 執行結果: 註意,當onclick ...
  • EF框架對資料庫的連接提供了一系列的預設行為,通常情況下不需要我們太多的關註。但是,這種封裝,降低了靈活性,有時我們需要對資料庫連接加以控制。 EF提供了兩種方案控制資料庫連接: 傳遞到Context的連接; Database.Connnection.Open(); 下麵詳解。 傳遞到Context ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...