[譯]聊聊C#中的泛型的使用(新手勿入)

来源:https://www.cnblogs.com/yilezhu/archive/2018/11/27/10029782.html
-Advertisement-
Play Games

寫在前面 今天忙裡偷閑在瀏覽外文的時候看到一篇講C 中泛型的使用的文章,因此加上本人的理解以及四級沒過的英語水平斗膽給大伙進行了翻譯,當然在翻譯的過程中發現了一些問題,因此也進行了糾正,當然,原文的地址我放在最下麵,如果你的英文水平比較好的話,可以直接直接閱讀全文。同時最近建了一個.NET Core ...


寫在前面

今天忙裡偷閑在瀏覽外文的時候看到一篇講C#中泛型的使用的文章,因此加上本人的理解以及四級沒過的英語水平斗膽給大伙進行了翻譯,當然在翻譯的過程中發現了一些問題,因此也進行了糾正,當然,原文的地址我放在最下麵,如果你的英文水平比較好的話,可以直接直接閱讀全文。同時最近建了一個.NET Core實戰項目交流群637326624,有興趣的朋友可以來相互交流。目前.NET Core實戰項目之CMS的教程也已經更新了6篇了,目前兩到三天更新一篇。

作者:依樂祝
原文地址:https://www.cnblogs.com/yilezhu/p/10029782.html

介紹

C#和.NET中的泛型程式具有強類型集合的許多優點,併為代碼提供更高質量和性能提升。泛型是C#語言和公共語言運行庫(CLR)中的一個新功能,它將類型參數的概念引入.NET Framework。類型參數使得設計某些類和方法成為可能,例如,通過使用泛型類型參數T,可以大大簡化類型之間的強制轉換或裝箱操作的過程(裝箱、拆箱問題)。說白了,泛型就是通過參數化類型來實現在同一份代碼上操作多種數據類型,利用“參數化類型”將類型抽象化,從而實現靈活的復用。每個集合的詳細規範可以在System.Collection.Generic名稱空間下找到。

通用類C#

裝箱和拆箱

.Net定義了兩種主要的數據類型來表示變數,也就是傳說中的值類型和引用類型。這是需要裝箱和拆箱的地方。裝箱是一種通過將變數存儲到System.Object中來顯式地將值類型轉換為引用類型的機制。當您裝入值時,CLR會將新對象分配到堆中,並將值類型的值複製到該實例中。例如,您創建了一個int類型的變數:

int a = 20;  
object b = a; //裝箱

相反的操作是拆箱,它是將引用類型轉換回值類型的過程。此過程驗證接收數據類型是否與裝箱類型一致;

int c = (int)b; // 拆箱

C#編譯器可以看到從int到object的賦值,反之亦然。當編譯該程式並通過IL解析器檢查IL生成的代碼時,您會註意到當b被賦值為a時,程式通過在IL中自動插入一個box指令來響應,當c被賦值為b時如下;

IL-opcode.jpg

代碼載入常量20並將其存儲在本地插槽中;它將值20載入到堆棧中並將其裝箱。最後,它將被裝箱的20返回到堆棧上,並將其拆箱為int類型

這個過程.NET CLR執行了一系列操作,例如,首先在托管堆中分配一個對象,然後在裝箱中將值轉換為記憶體位置,併在拆箱期間將值存儲在堆上並且必須轉回到堆棧。因此,從性能的角度來看,裝箱和拆箱過程在泛型中具有非常重要的意義,因為這個過程如果不使用泛型的話會耗費更多地資源。

泛型類

可以通過在類名後面加上符號來定義泛型類。這裡沒有強制必須將“T”字放在泛型的定義中。您可以在TestClass <>類聲明中使用任何單詞。

public class TestClass<T> { }  

System.Collection.Generic命名空間下還定義了許多實現了這些關鍵字介面的類型。下表列出了此命名空間的核心類類型。

泛型類 描述
Collection 泛型集合的基類,可以比較兩個泛型對象是否相等
Dictionary<TKey, TValue> 鍵值對的泛型集合
List 可動態調整列表項的大小
Queue 先進先出(FIFO)列表的泛型實現
Stack 後進先出(LIFO)列表的泛型實現

簡單的泛型類示例

以下示例顯示了一個簡單的泛型類型的操作。TestClass 定義一個長度為5的泛型類型數組。Add()方法負責將任何類型的對象添加到集合中,而Indexer屬性是迴圈語句迭代的實現。最後在主類中,我們使用整形類型來實例化TestClass 類,並使用Add()方法將一些整數類型數據添加到集合中。

using System;  
using System.Collections.Generic;  
  
namespace GenericApp  
{  
    public class TestClass<T>  
    {  
        // 定義一個長度為5的泛型類型的數組
        T[] obj = new T[5];  
        int count = 0;  
  
        // 向反省類型添加數據 
        public void  Add(T item)  
        {  
            //checking length  
            if (count + 1 < 6)  
            {  
                obj[count] = item;  
  
            }  
            count++;  
        }  
        //foreach語句迭代索引
        public T this[int index]  
        {  
            get { return obj[index]; }  
            set { obj[index] = value; }  
        }   
    }  
    class Program  
    {  
        static void Main(string[] args)  
        {  
            //用整形來實例化泛型類
            TestClass<int> intObj = new TestClass<int>();  
  
            //向集合中添加int數據
            intObj.Add(1);  
            intObj.Add(2);  
            intObj.Add(3);     //沒有裝箱 
            intObj.Add(4);   
            intObj.Add(5);  
  
            //遍歷顯示數據
            for (int i = 0; i < 5; i++)  
            {  
                Console.WriteLine(intObj[i]);   //沒有拆箱
            }  
            Console.ReadKey();    
        }  
    }  
}  

在構建並運行該程式之後,程式的輸出如下所示;

簡單仿製Example.jpg

泛型的主要特性

泛型類型的一些重要特征使它們相比傳統的非泛型類型具有如下的顯著特征:

  • 類型安全
  • 性能
  • 二進位代碼復用

類型安全

泛型最重要的特征之一是類型安全性。對於非泛型ArrayList類,如果使用對象類型,則可以向集合中添加任何類型,這些類型有時會導致嚴重的問題。下麵的示例顯示向ArrayList類型的集合添加一個整數、字元串和對象;

ArrayList obj = new ArrayList();  
obj.Add(50);  
obj.Add("Dog");  
obj.Add(new TestClass());  

現在,如果使用整數對象來使用foreach語句進行遍歷的話,當編譯器接受到代碼,但是因為集合中的所有元素都不是整數,所以會導致運行時異常;

foreach(int i in obj)  
{  
    Console.WriteLine(i);    
}  

編程的經驗法則是應該儘早檢測到錯誤。對於泛型類Test,泛型類型T定義允許哪些類型。通過使用Test的定義,只能向集合添加整型類型的數據。這時候當Add()方法具有以下無效參數的時候編譯器將不編譯代碼;

Test<int> obj = new Test<int>();  
obj.Add(50);  
obj.Add("Dog");            //編譯錯誤 
obj.Add(new TestClass());  //編譯錯誤

性能

在下麵的示例中,ArrayList類存儲對象,並且定義了Add()方法來存儲一些整型參數。因此,整數類型被裝箱。當使用foreach語句讀取ArrayList中的值時,將發生拆箱。

ArrayList  obj = new ArrayList();   
obj.Add(50);    //裝箱- 值類型轉換成引用類型 
int x= (int)obj[0]; //拆箱
foreach(int i in obj)  
{  
   Console.WriteLine(i);   // 拆箱
} 

註意:泛型比其他集合(如ArrayList)更快。

代替使用對象類型,TestClass類的泛型類型被定義為int,因此在從編譯器動態生成的類中將使用int類型。所以將不會發生裝箱和拆箱,如下所示;

TestClass<int> obj = new TestClass<int>();  
obj.Add(50);    //沒有裝箱 
int x= obj[0]; // 沒有拆箱
foreach(int i in obj)  
{  
   Console.WriteLine(i);   //沒有拆箱 
}  

二進位代碼復用

泛型類型提供了一種源代碼保護機制。泛型類可以定義一次,並且可以使用許多不同類型來進行實例化。泛型可以在一種CLR支持的語言中定義,並可以被另一種.NET語言使用。以下TestClass 使用int和string類型進行實例化:

TestClass<int> obj = new TestClass<int>();  
obj.Add(50);  
  
TestClass<string> obj1 = new TestClass<string>();  
Obj1.Add("hello");  

通用方法

雖然大多數開發人員通常會使用基類庫中的現有泛型類型,但也有可能會構建自己的泛型成員和自定義的泛型類型。

本示例的目的是構建一個交換方法,該方法可以使用單個類型參數對任何可能的數據類型(基於值或基於引用)進行操作。由於交換演算法的性質,傳入的參數將作為使用ref關鍵字修飾的引用類型來進行發送。

using System;  
using System.Collections.Generic;  
  
namespace GenericApp  
{  
    class Program  
    {  
        //泛型方法
        static void Swap<T>(ref T a, ref T b)  
        {  
            T temp;  
            temp = a;  
            a = b;  
            b = temp;  
        }  
        static void Main(string[] args)  
        {  
            //交換兩個整形數據
            int a = 40, b = 60;  
            Console.WriteLine("Before swap: {0}, {1}", a, b);  
  
            Swap<int>(ref a, ref b);  
  
            Console.WriteLine("After swap: {0}, {1}", a, b);  
  
            Console.ReadLine();  
        }  
    }  
}  

編譯此泛型方法實現的程式後,輸出如下所示;

通用-Methods.jpg

字典

字典也被稱為映射或散列表。它表示允許您基於關鍵字來訪問元素的數據結構。字典的一個重要特征是更快的查找; 您可以添加或刪除選項而不會產生性能開銷。

.Net提供了幾個字典類,例如Dictionary <TKey,TValue>。類型參數TKey和TValue分別表示關鍵字的類型和它可以存儲的值。

簡單的字典示例

以下示例演示使用泛型的簡單字典集合。在此程式中,將創建一個Dictionary類型對象,該對象接受int作為鍵,字元串作為值。然後我們將一些字元串值添加到字典集合中,最後顯示字典集合元素。

using System;  
using System.Collections.Generic;  
  
namespace GenericApp  
{  
    public class Program    
    {  
        static void Main(string[] args)  
        {  
            //定義一個字典集合
            Dictionary<int,string> dObj = new Dictionary<int,string>(5);  
  
            //向字典中添加類型
  
            dObj.Add(1,1,"Tom");    
            dObj.Add(2,"John");  
            dObj.Add(3, "Maria");  
            dObj.Add(4, "Max");  
            dObj.Add(5, "Ram");  
  
            //輸出數據
            for (int i = 1; i <= dObj.Count;i++)  
            {  
                Console.WriteLine(dObj[i]);  
            }  
            Console.ReadKey();  
        }  
    }  
}  

以下示例通過定義附加類emp來描述一些更複雜的問題,其中我們覆蓋ToString()方法以顯示特定員工的名稱和薪水。稍後在Main()方法中,創建一個新的Dictionary <TKey,TValue)的實例,其中鍵的類型為string,值為emp類型。構造函數分配2個元素的容量。emp對象和作為鍵的字元串值被添加到字典集合中。最後,使用foreach語句迭代集合元素並顯示在屏幕上。

using System;  
using System.Text;  
using System.Collections.Generic;  
  
namespace GenericApp  
{  
    public class emp   
    {  
        private string name;  
        private int salary;  
   
        public emp(string name,int salary)  
        {  
            this.name = name;  
            this.salary = salary;   
        }  
        public override string ToString()  
        {  
            StringBuilder sb = new StringBuilder(200);  
            sb.AppendFormat("{0},{1}",name,salary);  
  
            return sb.ToString();  
        }  
  
    }  
    public class Program    
    {  
        static void Main(string[] args)  
        {  
            //定義一個字典集合 
            Dictionary<string, emp> dObj = new Dictionary<string, emp>(2);  
  
            //向字典中添加元素
            emp tom = new emp("tom", 2000);  
            dObj.Add("tom",tom);   // 鍵,值 
            emp john = new emp("john", 4000);  
            dObj.Add("john",john);  
  
            //print data  
            foreach(Object str in dObj.Values)  
            {  
               Console.WriteLine(str);  
            }  
   
            Console.ReadKey();  
        }  
    }  
}  

隊列

隊列是一種特殊類型的容器,可確保以FIFO(先進先出)方式訪問元素。隊列集合最適合實現消息傳遞的組件。我們可以使用以下語法定義Queue集合對象:

Queue qObj = new Queue();

Queue集合的屬性,方法和其他規則定義都位於Sysyem.Collection命名空間下。下表定義了關鍵成員;

System.Collection.Queue成員 定義
Enqueue() 將對象添加到隊列的末尾。
Dequeue() 從隊列的開頭刪除對象。
Peek() 返回隊列開頭的對象而不刪除它。

下麵演示了一個基本的隊列類型的集合,將一些字元串類型值添加到集合中,最後使用while語句來顯示整個集合中的數據 。

using System;  
using System.Collections;  
  
namespace GenericApp  
{  
    class Program  
    {  
        static void Main(string[] args)  
        {  
            //定義一個隊列
            Queue qObj = new Queue();  
  
            //向隊列中添加字元串數據
            qObj.Enqueue("Tom");  
            qObj.Enqueue("Harry");  
            qObj.Enqueue("Maria");       
            qObj.Enqueue("john");  
  
            //顯示隊列中的數據 
            while(qObj.Count !=0 )  
            {  
                Console.WriteLine(qObj.Dequeue());  
            }  
  
            Console.ReadKey();    
        }  
    }  
}  

堆棧

Stack集合是LIFO的抽象(後進先出)。我們可以使用以下語法定義Stack集合對象:

Stack qObj = new Stack();

下表說明瞭堆棧的關鍵成員;

System.Collection.Stack成員 定義
Contains() 如果在集合中找到特定元素,則返回true。
Clear() 刪除集合的所有元素。
Peek() 預覽堆棧中的最新元素。
Push() 它將元素推入堆棧。
Pop() 返回並刪除堆棧的頂部元素。

以下演示了堆棧集合。首先,將數組類型對象引用到堆棧集合中。然後使用Pop()方法從堆棧中刪除集合中元素的值並顯示在屏幕上。

using System;  
using System.Collections;  
  
namespace GenericApp  
{  
    class Program  
    {  
        static void Main(string[] args)  
        {  
            int[] iArray = new int[] {1,2,3,4,5,6,7,8,9,10 };  
  
            //定義一個堆棧
            Stack sObj = new Stack(iArray);  
  
            Console.WriteLine("Total items="+sObj.Count);  
  
            //顯示集合數據
            for (int i = 0; i < sObj.Count;++i )  
            {  
                Console.WriteLine(sObj.Pop());  
            }  
  
            Console.ReadKey();    
        }  
    }  
}  

在使用泛型實現的另一個示例中,使用Push()方法將5個項添加到堆棧中。然後使用迴圈迭代輸出堆棧中的數據。堆棧的枚舉器不會刪除數據; 它只是以LIFO方式返回每個項目,如下所示:

using System;  
using System.Collections;  
  
namespace GenericApp  
{  
    class Program  
    {  
        static void Main(string[] args)  
        {  
  
             //定義一個堆棧
            Stack sObj = new Stack();

            //向集合添加數據
            for (int i = 0; i < 5; ++i)
            {
                sObj.Push(i+1);
            }
            Console.WriteLine("Total items=" + sObj.Count);
            //列印數據 
            foreach (int i in sObj)
            {
                Console.WriteLine(i);
            }
            Console.ReadKey();
        }  
    }  
}  

總結

今天忙裡偷閑,在瀏覽外文的時候看到一篇講泛型的文章,因此就加上自己的理解進行了相關翻譯,也加深了自己對泛型的理解!如果英文比較好的話可以直接訪問https://www.c-sharpcorner.com/UploadFile/84c85b/using-generics-with-C-Sharp/ 自行查看!當然,我在翻譯的過程中也發現了文中的一些錯誤,所以進行了更正!同時最近建了一個.NET Core實戰項目交流群637326624,有興趣的朋友可以來相互交流。目前.NET Core實戰項目之CMS的教程也已經更新了6篇了,目前兩到三天更新一篇。最後感謝大家的閱讀。


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

-Advertisement-
Play Games
更多相關文章
  • 1.進程: 進程(Process)是電腦中的程式關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操作系統結構的基礎。在早期面向進程設計的電腦結構中,進程是程式的基本執行實體;在當代面向線程設計的電腦結構中,進程是線程的容器。程式是指令、數據及其組織形式的描述,進程是程式的 ...
  • 思路 首先肯定要樹形dp,一直沒想到怎麼用左偏樹。如果不斷彈出又不斷地合併複雜度不就太高了。瞄了眼題解才知道可以直接用大根樹。然後記錄出當前這棵左偏樹的大小(樹裡面所有點的薪水之和)以及點的個數。然後不斷的刪點。直到薪水滿足條件為止。 ...
  • 題意 "題目鏈接" Sol 直接把序列複製一遍 尾碼數組即可 在前$N$個位置中取$rak$最小的輸出 cpp include using namespace std; const int MAXN = 1e6 + 10; inline int read() { char c = getchar() ...
  • 我們常說的鎖是單進程多線程鎖,在多線程併發編程中,用於線程之間的數據同步,保護共用資源的訪問。而分散式鎖,指在分散式環境下,保護跨進程、跨主機、跨網路的共用資源,實現互斥訪問,保證一致性。 架構圖: 分散式鎖獲取思路a、在獲取分散式鎖的時候在locker節點下創建臨時順序節點,釋放鎖的時候刪除該臨時 ...
  • 大家肯定都有過在餓了麽,或者在美團外賣下單的經歷,下完單後,超過一定的時間,訂單就被自動取消了。這就是延時任務。延時任務的應用場景相當廣泛,不僅僅上面所說的餓了嗎,美團外賣,還有12306,或者是淘寶,攜程等等 都有這樣的場景。這延時任務是怎麼實現的呢?跟著我,繼續看下去吧。 1.在SQL查詢,Se ...
  • 一. 初識python. 1. 認識電腦 CPU(大腦) 3GHZ + 記憶體(DDR4) + 主板 + 電源(心臟)+ 顯示器 + 鍵盤 +滑鼠+ 顯卡 + 硬碟 80MB/s 操作系統 windows 家用 linux 伺服器 macOS 家用+伺服器 電腦是一個高度集成的電子電路 高低電平. ...
  • 上一篇說過在整個DI框架中 是核心,但是如果直接看 派生類其實看不出也沒什麼東西,因為這個類型其實都是調用的其它對象方法,所以我們先來看看其它的類型 ServiceCallSite ServiceCallSite ​ 這個是一個服務訪問配置的類型,DI內部使用此類的派生類型進行封裝所需要實例化的信息 ...
  • HtmlAgilityPack.HtmlWeb hw = new HtmlAgilityPack.HtmlWeb(); HtmlAgilityPack.HtmlDocument doc = hw.Load("C:\\Users\\u1\\Desktop\\test html.html"); Html... ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...