C#泛型詳解
這篇文章主要講解C#中的泛型,泛型在C#中有很重要的地位,尤其是在搭建專案框架的時候。
一、什麼是泛型
泛型是C#2.0推出的新語法,不是語法糖,而是2.0由框架升級提供的功能。
我們在程式設計程式時,經常會遇到功能非常相似的模組,只是它們處理的資料不一樣。但我們沒有辦法,只能分別寫多個方法來處理不同的資料型別。這個時候,那麼問題來了,有沒有一種辦法,用同一個方法來處理傳入不同種類型引數的辦法呢?泛型的出現就是專門來解決這個問題的。
二、為什麼使用泛型
先來看下面一個例子:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyGeneric
{
public class CommonMethod
{
/// <summary>
/// 列印個int值
///
/// 因為方法宣告的時候,寫死了引數型別
/// 已婚的男人 Eleven San
/// </summary>
/// <param name="iParameter"></param>
public static void ShowInt(int iParameter)
{
Console.WriteLine("This is {0},parameter={1},type={2}",typeof(CommonMethod).Name,iParameter.GetType().Name,iParameter);
}
/// <summary>
/// 列印個string值
/// </summary>
/// <param name="sParameter"></param>
public static void ShowString(string sParameter)
{
Console.WriteLine("This is {0},sParameter.GetType().Name,sParameter);
}
/// <summary>
/// 列印個DateTime值
/// </summary>
/// <param name="oParameter"></param>
public static void ShowDateTime(DateTime dtParameter)
{
Console.WriteLine("This is {0},dtParameter.GetType().Name,dtParameter);
}
}
}
View Code
結果:
從上面的結果中我們可以看出這三個方法,除了傳入的引數不同外,其裡面實現的功能都是一樣的。在1.0版的時候,還沒有泛型這個概念,那麼怎麼辦呢。相信很多人會想到了OOP三大特性之一的繼承,我們知道,C#語言中,object是所有型別的基類,將上面的程式碼進行以下優化:
public static void ShowObject(object oParameter)
{
Console.WriteLine("This is {0},typeof(CommonMethod),oParameter.GetType().Name,oParameter);
}
結果:
從上面的結果中我們可以看出,使用Object型別達到了我們的要求,解決了程式碼的可複用。可能有人會問定義的是object型別的,為什麼可以傳入int、string等型別呢?原因有二:
1、object型別是一切型別的父類。
2、通過繼承,子類擁有父類的一切屬性和行為,任何父類出現的地方,都可以用子類來代替。
但是上面object型別的方法又會帶來另外一個問題:裝箱和拆箱,會損耗程式的效能。
微軟在C#2.0的時候推出了泛型,可以很好的解決上面的問題。
三、泛型型別引數
在泛型型別或方法定義中,型別引數是在其例項化泛型型別的一個變數時,客戶端指定的特定型別的佔位符。 泛型類(GenericList<T>
GenericList<T>
,客戶端程式碼必須通過指定尖括號內的型別引數來宣告並例項化構造型別。 此特定類的型別引數可以是編譯器可識別的任何型別。 可建立任意數量的構造型別例項,其中每個使用不同的型別引數。
上面例子中的程式碼可以修改如下:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6
7 namespace MyGeneric
8 {
9 public class GenericMethod
10 {
11 /// <summary>
12 /// 泛型方法
13 /// </summary>
14 /// <typeparam name="T"></typeparam>
15 /// <param name="tParameter"></param>
16 public static void Show<T>(T tParameter)
17 {
18 Console.WriteLine("This is {0},type={2}",19 typeof(GenericMethod),tParameter.GetType().Name,tParameter.ToString());
20 }
21 }
22 }
呼叫:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyGeneric
{
class Program
{
static void Main(string[] args)
{
int iValue = 123;
string sValue = "456";
DateTime dtValue = DateTime.Now;
Console.WriteLine("***********CommonMethod***************");
CommonMethod.ShowInt(iValue);
CommonMethod.ShowString(sValue);
CommonMethod.ShowDateTime(dtValue);
Console.WriteLine("***********Object***************");
CommonMethod.ShowObject(iValue);
CommonMethod.ShowObject(sValue);
CommonMethod.ShowObject(dtValue);
Console.WriteLine("***********Generic***************");
GenericMethod.Show<int>(iValue);
GenericMethod.Show<string>(sValue);
GenericMethod.Show<DateTime>(dtValue);
Console.ReadKey();
}
}
}
View Code
顯示結果:
為什麼泛型可以解決上面的問題呢?
泛型是延遲宣告的:即定義的時候沒有指定具體的引數型別,把引數型別的宣告推遲到了呼叫的時候才指定引數型別。 延遲思想在程式架構設計的時候很受歡迎。例如:分散式快取佇列、EF的延遲載入等等。
泛型究竟是如何工作的呢?
控制檯程式最終會編譯成一個exe程式,exe被點選的時候,會經過JIT(即時編譯器)的編譯,最終生成二進位制程式碼,才能被計算機執行。泛型加入到語法以後,VS自帶的編譯器又做了升級,升級之後編譯時遇到泛型,會做特殊的處理:生成佔位符。再次經過JIT編譯的時候,會把上面編譯生成的佔位符替換成具體的資料型別。請看下面一個例子:
1 Console.WriteLine(typeof(List<>));
2 Console.WriteLine(typeof(Dictionary<,>));
結果:
從上面的截圖中可以看出:泛型在編譯之後會生成佔位符。
注意:佔位符需要在英文輸入法狀態下才能輸入,只需要按一次波浪線(數字1左邊的鍵位)的鍵位即可,不需要按Shift鍵。
1、泛型效能問題
請看一下的一個例子,比較普通方法、Object引數型別的方法、泛型方法的效能。
新增一個Monitor類,讓三種方法執行同樣的操作,比較用時長短:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6
7 namespace MyGeneric
8 {
9 public class Monitor
10 {
11 public static void Show()
12 {
13 Console.WriteLine("****************Monitor******************");
14 {
15 int iValue = 12345;
16 long commonSecond = 0;
17 long objectSecond = 0;
18 long genericSecond = 0;
19
20 {
21 Stopwatch watch = new Stopwatch();
22 watch.Start();
23 for (int i = 0; i < 100000000; i++)
24 {
25 ShowInt(iValue);
26 }
27 watch.Stop();
28 commonSecond = watch.ElapsedMilliseconds;
29 }
30 {
31 Stopwatch watch = new Stopwatch();
32 watch.Start();
33 for (int i = 0; i < 100000000; i++)
34 {
35 ShowObject(iValue);
36 }
37 watch.Stop();
38 objectSecond = watch.ElapsedMilliseconds;
39 }
40 {
41 Stopwatch watch = new Stopwatch();
42 watch.Start();
43 for (int i = 0; i < 100000000; i++)
44 {
45 Show<int>(iValue);
46 }
47 watch.Stop();
48 genericSecond = watch.ElapsedMilliseconds;
49 }
50 Console.WriteLine("commonSecond={0},objectSecond={1},genericSecond={2}"
51 ,commonSecond,objectSecond,genericSecond);
52 }
53 }
54
55 #region PrivateMethod
56 private static void ShowInt(int iParameter)
57 {
58 //do nothing
59 }
60 private static void ShowObject(object oParameter)
61 {
62 //do nothing
63 }
64 private static void Show<T>(T tParameter)
65 {
66 //do nothing
67 }
68 #endregion
69
70 }
71 }
Main()方法呼叫:
1 Monitor.Show();
結果:
從結果中可以看出:泛型方法的效能最高,其次是普通方法,object方法的效能最低。
四、泛型類
除了方法可以是泛型以外,類也可以是泛型的,例如:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6
7 namespace MyGeneric
8 {
9 /// <summary>
10 /// 泛型類
11 /// </summary>
12 /// <typeparam name="T"></typeparam>
13 public class GenericClass<T>
14 {
15 public T _T;
16 }
17 }
Main()方法中呼叫:
1 // T是int型別
2 GenericClass<int> genericInt = new GenericClass<int>();
3 genericInt._T = 123;
4 // T是string型別
5 GenericClass<string> genericString = new GenericClass<string>();
6 genericString._T = "123";
除了可以有泛型類,也可以有泛型介面,例如:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6
7 namespace MyGeneric
8 {
9 /// <summary>
10 /// 泛型介面
11 /// </summary>
12 public interface IGenericInterface<T>
13 {
14 //泛型型別的返回值
15 T GetT(T t);
16 }
17 }
也可以有泛型委託:
1 public delegate void SayHi<T>(T t);//泛型委託
注意:
1、泛型在宣告的時候可以不指定具體的型別,但是在使用的時候必須指定具體型別,例如:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6
7 namespace MyGeneric
8 {
9 /// <summary>
10 /// 使用泛型的時候必須指定具體型別,
11 /// 這裡的具體型別是int
12 /// </summary>
13 public class CommonClass :GenericClass<int>
14 {
15 }
16 }
如果子類也是泛型的,那麼繼承的時候可以不指定具體型別,例如:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6
7 namespace MyGeneric
8 {
9 /// <summary>
10 /// 使用泛型的時候必須指定具體型別,
11 /// 這裡的具體型別是int
12 /// </summary>
13 public class CommonClass :GenericClass<int>
14 {
15 }
16
17 /// <summary>
18 /// 子類也是泛型的,繼承的時候可以不指定具體型別
19 /// </summary>
20 /// <typeparam name="T"></typeparam>
21 public class CommonClassChild<T>:GenericClass<T>
22 {
23
24 }
25 }
2、類實現泛型介面也是這種情況,例如:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6
7 namespace MyGeneric
8 {
9 /// <summary>
10 /// 必須指定具體型別
11 /// </summary>
12 public class Common : IGenericInterface<string>
13 {
14 public string GetT(string t)
15 {
16 throw new NotImplementedException();
17 }
18 }
19
20 /// <summary>
21 /// 可以不知道具體型別,但是子類也必須是泛型的
22 /// </summary>
23 /// <typeparam name="T"></typeparam>
24 public class CommonChild<T> : IGenericInterface<T>
25 {
26 public T GetT(T t)
27 {
28 throw new NotImplementedException();
29 }
30 }
31 }
五、泛型約束
先來看看下面的一個例子:
定義一個People類,裡面有屬性和方法:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6
7 namespace MyGeneric
8 {
9 public interface ISports
10 {
11 void Pingpang();
12 }
13
14 public interface IWork
15 {
16 void Work();
17 }
18
19
20 public class People
21 {
22 public int Id { get; set; }
23 public string Name { get; set; }
24
25 public void Hi()
26 {
27 Console.WriteLine("Hi");
28 }
29 }
30
31 public class Chinese : People,ISports,IWork
32 {
33 public void Tradition()
34 {
35 Console.WriteLine("仁義禮智信,溫良恭儉讓");
36 }
37 public void SayHi()
38 {
39 Console.WriteLine("吃了麼?");
40 }
41
42 public void Pingpang()
43 {
44 Console.WriteLine("打乒乓球...");
45 }
46
47 public void Work()
48 {
49 throw new NotImplementedException();
50 }
51 }
52
53 public class Hubei : Chinese
54 {
55 public Hubei(int version)
56 { }
57
58 public string Changjiang { get; set; }
59 public void Majiang()
60 {
61 Console.WriteLine("打麻將啦。。");
62 }
63 }
64
65
66 public class Japanese : ISports
67 {
68 public int Id { get; set; }
69 public string Name { get; set; }
70 public