C# 之泛型詳解

来源:https://www.cnblogs.com/Liyuting/archive/2018/03/28/8666513.html
-Advertisement-
Play Games

這篇文章主要來講講c#中的泛型,因為泛型在c#中有很重要的位置,對於寫出高可讀性,高性能的代碼有著關鍵的作用。 一、什麼是泛型? 泛型是 2.0 版 C# 語言和公共語言運行庫 (CLR) 中的一個非常重要的新功能。 我們在編程程式時,經常會遇到功能非常相似的模塊,只是它們處理的數據不一樣。但我們沒 ...


 

 

這篇文章主要來講講c#中的泛型,因為泛型在c#中有很重要的位置,對於寫出高可讀性,高性能的代碼有著關鍵的作用。

一、什麼是泛型?

泛型是 2.0 版 C# 語言和公共語言運行庫 (CLR) 中的一個非常重要的新功能。

我們在編程程式時,經常會遇到功能非常相似的模塊,只是它們處理的數據不一樣。但我們沒有辦法,只能分別寫多個方法來處理不同的數據類型。這個時候,那麼問題來了,有沒有一種辦法,用同一個方法來處理傳入不同種類型參數的辦法呢?泛型的出現就是專門來解決這個問題的,可以看出,微軟還是很貼心的。

二、為什麼要使用泛型?

接下來我們來看一段代碼。

public class GenericClass
    {
        public void ShowInt(int n)
        {
            Console.WriteLine("ShowInt print {0},ShowInt Parament Type Is {1}",n,n.GetType());
        }
        public void ShowDateTime(DateTime dt)
        {
            Console.WriteLine("ShowDateTime print {0},ShowDateTime Parament Type Is {1}", dt, dt.GetType());
        }
        public void ShowPeople(People people)
        {
            Console.WriteLine("ShowPeople print {0},ShowPeople Parament Type Is {1}", people, people.GetType());
        }
    }
static void Main(string[] args)
        {
            GenericClass generice = new GenericClass();
            generice.ShowInt(11);
            generice.ShowDateTime(DateTime.Now);
            generice.ShowPeople(new People { Id = 11, Name = "Tom" });

            Console.ReadKey();
        }

顯示結果:

我們可以看出這三個方法,除了傳入的參數不同外,其裡面實現的功能都是一樣的。在1.1版的時候,還沒有泛型這個概念,那麼怎麼辦呢。就有人想到了OOP三大特性之一的繼承,我們知道,C#語言中,所有類型都源自同一個類型,那就是object。

public class GenericClass
    {
        public void ShowObj(object obj)
        {
            Console.WriteLine("ShowObj print {0},ShowObj Parament Type Is {1}", obj, obj.GetType());
        }
    }
        static void Main(string[] args)
        {
            Console.WriteLine("*****************object調用*********************");
            generice.ShowObj(11);
            generice.ShowObj(DateTime.Now);
            generice.ShowObj(new People { Id = 11, Name = "Tom" });

            Console.ReadKey();
        }

顯示結果:

我們可以看出,目地是達到了。解決了代碼的可讀性,但是這樣又有個不好的地方了,我們這樣做實際上是一個裝箱拆箱操作,會損耗性能。

終於,微軟在2.0的時候發佈了泛型。接下來我們用泛型方法來實現該功能。

三、泛型類型參數

在使用泛型方法之前,我們先來瞭解下有關於泛型的一些知識。

在泛型類型或方法定義中,類型參數是在其實例化泛型類型的一個變數時,客戶端指定的特定類型的占位符。 泛型類( GenericList<T>)無法按原樣使用,因為它不是真正的類型;它更像是類型的藍圖。 若要使用 GenericList<T>,客戶端代碼必須通過指定尖括弧內的類型參數來聲明並實例化構造類型。 此特定類的類型參數可以是編譯器可識別的任何類型。 可創建任意數量的構造類型實例,其中每個使用不同的類型參數,如下所示:

GenericList<float> list1 = new GenericList<float>();
GenericList<ExampleClass> list2 = new GenericList<ExampleClass>();
GenericList<ExampleStruct> list3 = new GenericList<ExampleStruct>();

在 GenericList<T> 的每個實例中,類中出現的每個 T 在運行時均會被替換為類型參數。 通過這種替換,我們已通過使用單個類定義創建了三個單獨的類型安全的有效對象。 

三、泛型約束

定義泛型類時,可以對客戶端代碼能夠在實例化類時用於類型參數的幾種類型施加限制。 如果客戶端代碼嘗試使用約束所不允許的類型來實例化類,則會產生編譯時錯誤。 這些限制稱為約束。 通過使用 where 上下文關鍵字指定約束。 下表列出了六種類型的約束:

where T:結構(類型參數必須是值類型。可以指定除 Nullable 以外的任何值類型。)

class MyClass<U>
        where U : struct///約束U參數必須為“值 類型”
 { }

 public void MyMetod<T>(T t)
       where T : struct
 {          
 }

where T:類(類型參數必須是引用類型;這一點也適用於任何類、介面、委托或數組類型。)

class MyClass<U>
        where U : class///約束U參數必須為“引用類型”
 { }

 public void MyMetod<T>(T t)
       where T : class
 {          
 }

where T:new()(類型參數必須具有無參數的公共構造函數。當與其他約束一起使用時,new() 約束必須最後指定。)

class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new()
{
    // ...
}

where T:<基類名>(類型參數必須是指定的基類或派生自指定的基類。)

public class Employee{}

public class GenericList<T> where T : Employee

where T:<介面名稱>(類型參數必須是指定的介面或實現指定的介面。可以指定多個介面約束。約束介面也可以是泛型的。)

/// <summary>
    /// 介面
    /// </summary>
    interface IMyInterface
    {
    }

    /// <summary>
    /// 定義的一個字典類型
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    /// <typeparam name="TVal"></typeparam>
    class Dictionary<TKey, TVal>
        where TKey : IComparable, IEnumerable
        where TVal : IMyInterface
    {
        public void Add(TKey key, TVal val)
        {
        }
    }

where T:U(為 T 提供的類型參數必須是為 U 提供的參數或派生自為 U 提供的參數。也就是說T和U的參數必須一樣)

class List<T>
{
    void Add<U>(List<U> items) where U : T {/*...*/}
}

以上就是對六種泛型的簡單示例,當然泛型約束不僅僅適用於類,介面,對於泛型方法,泛型委托都同樣適用。

三、泛型方法

public class GenericClass
    {
        public void ShowT<T>(T t)
        {
            Console.WriteLine("ShowT print {0},ShowT Parament Type Is {1}", t, t.GetType());
        }
    }
static void Main(string[] args)
        {
            Console.WriteLine("*****************泛型方法調用*********************");
            generice.ShowT<int>(11);
            generice.ShowT<DateTime>(DateTime.Now);
            generice.ShowT<People>(new People { Id = 11, Name = "Tom" });

            Console.ReadKey();
        }

顯示結果:

也是一樣的,現在終於實現了我們想要達到的效果了。我們可以看出,無論是什麼方式調用,最後我們獲取出來的類型都是原始類型。我們知道,用object獲取是利用了繼承這一特性,當編譯器編譯的時候,我們傳入的參數會進行裝箱操作,當我們獲取的時候又要進行拆箱操作,這個方法會損耗性能 。那麼泛型方法實現的原理又是怎樣的呢?首先,我們要知道,泛型是一個語法糖,在我們調用泛型方法,編譯器進行編譯時,才會確定傳入的參數的類型,從而生成副本方法。這個副本方法與原始方法一法,所以不會有裝箱拆箱操作,也就沒有損耗性能這回事了。

四、泛型類

泛型類封裝不特定於特定數據類型的操作。

通常,創建泛型類是從現有具體類開始,然後每次逐個將類型更改為類型參數,直到泛化和可用性達到最佳平衡。

創建自己的泛型類時,需要考慮以下重要註意事項:

  • 要將哪些類型泛化為類型參數。

               通常,可參數化的類型越多,代碼就越靈活、其可重用性就越高。 但過度泛化會造成其他開發人員難以閱讀或理解代碼。

  • 要將何種約束(如有)應用到類型參數

 

         其中一個有用的規則是,應用最大程度的約束,同時仍可處理必須處理的類型。 例如,如果知道泛型類僅用於引用類型,則請應用類約束。 這可防止將類意外用於值類型,並     使你可在   T  上使用  as  運算符和檢查 null 值。      

 

  • 是否將泛型行為分解為基類和子類。

    因為泛型類可用作基類,所以非泛型類的相同設計註意事項在此也適用。 請參閱本主題後文有關從泛型基類繼承的規則。

  • 實現一個泛型介面還是多個泛型介面。

  • class BaseNode { }
    class BaseNodeGeneric<T> { }
    
    // concrete type
    class NodeConcrete<T> : BaseNode { }
    
    //closed constructed type
    class NodeClosed<T> : BaseNodeGeneric<int> { }
    
    //open constructed type 
    class NodeOpen<T> : BaseNodeGeneric<T> { }

    五、泛型介面

    • 定義一個泛型介面:
    • interface IMyGenericInterface<T>
      {
      }
      • 一個介面可定義多個類型參數,如下所示:
      • interface IMyGenericInterface<TKey,TValue>
        {
        }
        • 具體類可實現封閉式構造介面,如下所示:
        • interface IBaseInterface<T> { }
          
          class SampleClass : IBaseInterface<string> { }//如果T有約束,那麼string類型必須得滿足T的約束

六、泛型委托

委托可以定義它自己的類型參數。 引用泛型委托的代碼可以指定類型參數以創建封閉式構造類型,就像實例化泛型類或調用泛型方法一樣,如以下示例中所示:

class Program
    {
        static void Main(string[] args)
        {
            Del<int> m1 = new Del<int>(Notify);
            m1.Invoke(1111);
            Del<string> m2 = new Del<string>(Notify);
            m2.Invoke("字元串");

            Console.ReadKey();
        }

        public delegate void Del<T>(T item);
        public static void Notify(int i) { Console.WriteLine("{0} type is {1}", i,i.GetType()); }
        public static void Notify(string str) { Console.WriteLine("{0} type is {1}", str, str.GetType()); }
       
    }

運行結果:

七、泛型代碼中的預設關鍵字:Default

在泛型類和泛型方法中產生的一個問題是,在預先未知以下情況時,如何將預設值分配給參數化類型 T:

  • T 是引用類型還是值類型。

  • 如果 T 為值類型,則它是數值還是結構。

給定參數化類型 T 的一個變數 t,只有當 T 為引用類型時,語句 t = null 才有效;只有當 T 為數值類型而不是結構時,語句 t = 0 才能正常使用。解決方案是使用 default 關鍵字,此關鍵字對於引用類型會返回空,對於數值類型會返回零。對於結構,此關鍵字將返回初始化為零或空的每個結構成員,具體取決於這些結構是值類型還是引用類型。

namespace MyGeneric
{
    class Program
    {
        static void Main(string[] args)
        {
            object obj1=GenericToDefault<string>();
            object obj2 = GenericToDefault<int>();
            object obj3 = GenericToDefault<StructDemo>();
            Console.ReadKey();
        }
        public static T GenericToDefault<T>() 
        {
            return default(T);
        }
    }
    public struct StructDemo
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}

運行結果:

 

 

  原文鏈接:https://www.cnblogs.com/hhzblogs/p/7820005.html

 


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

-Advertisement-
Play Games
更多相關文章
  • 描述 佈置宴席最微妙的事情,就是給前來參宴的各位賓客安排座位。無論如何,總不能把兩個死對頭排到同一張宴會桌旁!這個艱巨任務現在就交給你,對任何一對客人,請編寫程式告訴主人他們是否能被安排同席。 佈置宴席最微妙的事情,就是給前來參宴的各位賓客安排座位。無論如何,總不能把兩個死對頭排到同一張宴會桌旁!這 ...
  • 傳送門(bzoj) 傳送門(luogu) 題目: Description 小西有一條很長的彩帶,彩帶上掛著各式各樣的彩珠。已知彩珠有N個,分為K種。簡單的說,可以將彩帶考慮為x軸,每一個彩珠有一個對應的坐標(即位置)。某些坐標上可以沒有彩珠,但多個彩珠也可以出現在同一個位置上。 小布生日快到了,於是 ...
  • OOP的好處,關鍵的OOP概念,構造函數和析構函數,靜態類成員,instanceof關鍵字,輔助函數,自動載入函數 ...
  • 原文發佈在特克斯博客www.susmote.com 之前給大家講了關於python的背景知識,還有Python的優點和缺點,相信通過之前的介紹很多人已經清楚自己到底要不要選擇學習Python,如果已經很有興趣了,那麼你就可以自己查看一些有關Python的官方文檔,或是買本書啃啃,如果你暫時還沒什麼興 ...
  • .NET Core是一個開源通用的開發框架,具有跨平臺能力,我們在享受其性能飆升的同時,也面臨了一些問題。通過觀察 NetCore 程式的線上運行情況發現 ,負載高的情況下應用程式占用記憶體較大,本文將針對這個問題展開討論,對比分析不同GC工作模式下的.NetCore性能與記憶體管理的表現。通過查找資料 ...
  • 看圖: 這裡可以看到是二層嵌套!!使用C#如何實現?? 思路:使用list集合實現 → 建立類 → list集合 → 微軟的 Newtonsoft.Json (一款.NET中開源的Json序列化和反序列化) sonXMText類 using System; using System.Collecti ...
  • IEnumerator和IEnumerable 從名字常來看,IEnumerator是枚舉器的意思,IEnumerable是可枚舉的意思。 瞭解了兩個介面代表的含義後,接著看源碼: IEnumerator: IEnumerable: 發現IEnumerable只有一個GetEnumerator函數, ...
  • 因為今天是最後一天了,我趕緊在這次結束前提出一些前一晚上想到的問題 1. 在TDD的循環中有重構,那 DB 也會進行重構嗎? 在TDD 的重構的過程,其實也經常會重構資料庫 , 但重構資料庫這裡有一個很重要的點,是要如何做 DB Migration from : Odd-e CSD Course 經 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...