[譯]聊聊C#中的泛型的使用(新手勿入)
寫在前面
今天忙裡偷閒在瀏覽外文的時候看到一篇講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名稱空間下找到。
裝箱和拆箱
.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時如下;
程式碼載入常量20並將其儲存在本地插槽中;它將值20載入到堆疊中並將其裝箱。最後,它將被裝箱的20返回到堆疊上,並將其拆箱為int型別
這個過程.NET CLR執行了一系列操作,例如,首先在託管堆中分配一個物件,然後在裝箱中將值轉換為記憶體位置,並在拆箱期間將值儲存在堆上並且必須轉回到堆疊。因此,從效能的角度來看,裝箱和拆箱過程在泛型中具有非常重要的意義,因為這個過程如果不使用泛型的話會耗費更多地資源。
泛型類
可以通過在類名後面加上
public class TestClass<T> { }
System.Collection.Generic名稱空間下還定義了許多實現了這些關鍵字介面的型別。下表列出了此名稱空間的核心類型別。
泛型類 | 描述 |
---|---|
Collection
|
泛型集合的基類,可以比較兩個泛型物件是否相等 |
Dictionary<TKey, TValue> | 鍵值對的泛型集合 |
List
|
可動態調整列表項的大小 |
Queue
|
先進先出(FIFO)列表的泛型實現 |
Stack
|
後進先出(LIFO)列表的泛型實現 |
簡單的泛型類示例
以下示例顯示了一個簡單的泛型型別的操作。TestClass
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();
}
}
}
在構建並執行該程式之後,程式的輸出如下所示;
泛型的主要特性
泛型型別的一些重要特徵使它們相比傳統的非泛型型別具有如下的顯著特徵:
- 型別安全
- 效能
- 二進位制程式碼複用
型別安全
泛型最重要的特徵之一是型別安全性。對於非泛型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
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
TestClass<int> obj = new TestClass<int>();
obj.Add(50); //沒有裝箱
int x= obj[0]; // 沒有拆箱
foreach(int i in obj)
{
Console.WriteLine(i); //沒有拆箱
}
二進位制程式碼複用
泛型型別提供了一種原始碼保護機制。泛型類可以定義一次,並且可以使用許多不同型別來進行例項化。泛型可以在一種CLR支援的語言中定義,並可以被另一種.NET語言使用。以下TestClass
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();
}
}
}
編譯此泛型方法實現的程式後,輸出如下所示;
字典
字典也被稱為對映或散列表。它表示允許您基於關鍵字來訪問元素的資料結構。字典的一個重要特徵是更快的查詢; 您可以新增或刪除選項而不會產生效能開銷。
.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篇了,目前兩到三天更新一篇。最後感謝大家的閱讀。