泛型的學習
泛型的學習
一、泛型的引入
- 泛型---泛:寬泛的--不確定的; 型:類型---不確定的類型
- 無處不在的
- 調用普通方法的時候,參數類型在聲明的時候就確定了,調用按照類型傳遞參數即可
a. 如果有100個類型---100個方法?--很累
b. 有沒有能夠做一個方法可以能夠滿足不同類型的需求呢?
傳統方法
public static class CommonMethod
{
public static void ShowInt(int Parameter)
{
Console.WriteLine($"This is {typeof(CommonMethod).Name} parameter={Parameter},type={Parameter.GetType().Name} ");
}
public static void ShowString(string Parameter)
{
Console.WriteLine($"This is {typeof(CommonMethod).Name} parameter={Parameter},type={Parameter.GetType().Name} ");
}
public static void ShowDateTime(DateTime Parameter)
{
Console.WriteLine($"This is {typeof(CommonMethod).Name} parameter={Parameter},type={Parameter.GetType().Name} ");
}
}
- Object類型作為參數 ----可以傳遞不同的參數
a. 任何子類出現的地址都可以讓父類來代替
b. 萬物皆對象---任何一個類型都是繼承自Object
使用Object類型
public static class CommonMethod
{
public static void ShowObject(object Parameter)
{
Console.WriteLine($"This is {typeof(CommonMethod).Name} parameter={Parameter},type={Parameter.GetType().Name} ");
}
}
- 問題
a. 性能問題 ---裝箱拆箱---在c#語法中,按照聲明時決定類型的(棧、托管堆)
b. 類型安全問題
我們通過一個例子來體現性能的問題
public class PerformanceTest
{
public static void Show()
{
int ivalue = 1234;
//消耗的時間
long commonSecond = 0;
long objectSecond = 0;
long genericSecond = 0;
{
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 1000_000_000; i++) {
ShowInt(ivalue);
}
sw.Stop();
commonSecond = sw.ElapsedMilliseconds;
}
{
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 1000_000_000; i++)
{
ShowObject(ivalue);
}
sw.Stop();
objectSecond = sw.ElapsedMilliseconds;
}
{
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 1000_000_000; i++)
{
Show(ivalue);
}
sw.Stop();
genericSecond = sw.ElapsedMilliseconds;
}
Console.WriteLine($"commonSecond: {commonSecond} objectSecond: {objectSecond} genericSecond :{genericSecond} ");
}
private static void ShowInt(int r)
{
}
private static void ShowObject(object o)
{
}
private static void Show<T>(T parameter)
{
}
}
結果為:

- 有沒有既性能好,也能夠支持多種類型的方法呢?---泛型方法
a. 聲明多了一對尖括號 + 占位符T
b. 調用--也需要多一對尖括號,尖括號中指定的類型要和傳遞的參數的類型一致。
c. 如果可以參數推到出類型---尖括號可以省略。 - 泛型方法---做到了性能高---可以一個方法滿足不同類的需求:---又讓馬兒跑,又讓馬吃草
二、泛型的聲明
1.泛型方法:在方法名稱后面多一對尖括號,尖括號中有占位符
2.延遲聲明:聲明的時候,只是給一個占位符T,T是什么類型?不知道什么類型---調用的時候,指定你是什么,調用的時候,你說什么就是什么;
3.占位符: T ---類型參數 --- 類型變量
4.類型參數當作方法的參數的時候,明確參數類型。
三、泛型的特點+原理 -- 底層如何支持的?
- 在高級語言中,定義的泛型T,在計算機執行的時候一定要是一個具體的類型。
- 在底層如何支持?---在底層看到,生成的結果是LIST
1[T] Dictionary2[TKey,TValue] - 在底層---生成了
1、2、3、4、5、6 - 編譯器必須要能夠支持泛型
- CLR運行時環境也需要要支持泛型
- 泛型當然是框架的升級支持的---泛型不是語法糖---有框架的升級支持的;
- 語法糖:是編譯器提供的便捷功能。
四、泛型的多種應用
- 泛型方法---可以一個方法滿足不同類型的需求
- 泛型接口---可以一個接口滿足不同類型的需求 --- 尖括號+占位符
- 泛型類-----可以一個類型滿足不同類型的需求
- 泛型委托---可以一個委托滿足不同類型的需求
尖括號中可以有多個類型參數。
public class GenericsTest
{
public interface GenericsInterface<T>
{
public T show();
}
public class GenericsClass<T>
{
public void Show(T t)
{
Console.WriteLine(t);
}
}
public delegate T genericsDelegate<T>();
//1.泛型方法
static void Show<T>(T value)
{
Console.WriteLine(value);
}
//2.泛型接口
GenericsInterface<string> genericsString = null;
GenericsInterface<DateTime> genericsDatetime = null;
//3.泛型類
GenericsClass<string> genericsClassString = null;
GenericsClass<DateTime> genericsClassDateTime = null;
//4.泛型委托
genericsDelegate<string> genericsDelegateString=null;
genericsDelegate<DateTime> genericsDelegateDateTime=null;
}
類型參數一定要為T嗎?不一定,也可以是其他的名字(不要使用關鍵字)。
并且尖括號中可以有多個類型參數
public class GenericsClass<T,S,X,GODLWL>
{
public void Show(T t)
{
Console.WriteLine(t);
}
}
繼承抽象泛型類的情況
//使用T報錯,為什么不行?要繼承的時候,必須要確定類型,
public abstract class Children:FatherClass<T>
{
}
//不報錯,在子類實例化的時候,父類的類型也會被確定。
public class Children1<S> : FatherClass<S>
{
}
繼承類的狀況
public class Children2<S>
{
public S show()
{
return default(S);
}
public S show(S s)
{
return s;
}
}
五、類型安全解讀
要有真正的自由--就需要要有約束--開車---交通規則--紅綠燈
- Object類型
public class Genericconstraints
{
public class People
{
public int Id { get; set; }
public string Name { get; set; }
}
public static void ShowObject(object oValue)
{
//沒有意義的打印輸出
//Console.WriteLine(oValue);
//傳遞一個實體的對象:操作字段和方法
//問題:
//1.無法去屬性字段--因為oValue是Object;C#是強類型語言,編譯時決定參數是什么類型;
//Console.WriteLine($"People.Id={oValue.Id}");
//Console.WriteLine($"People.Name={oValue.Name}");
//2、強制轉換
People people = (People)oValue;
Console.WriteLine($"People.Id={people.Id}");
Console.WriteLine($"People.Name={people.Name}");
}
}
當我們傳遞一個INT類型的變量的時候,運行的時候就會報錯。
為了避免這種類型安全的問題,我們就引入了泛型的約束。
六、泛型的約束
- 如何避免泛型的類型安全問題,引入了泛型約束。
//基類約束
//a.就是把類型參數當做People
//b.調用---就可以傳遞People或者People的子類型
//c.泛型約束:要么不讓你進來;如果讓你進來,就一定是沒問題的
public static void Show<T>(T tValue) where T : People
{
Console.WriteLine($"People.Id={tValue.Id}");
Console.WriteLine($"People.Name={tValue.Name}");
}
public interface ISports
{
public void run();
}
public class Cat:ISports
{
public void run()
{
Console.WriteLine("Cat run");
}
}
//接口約束
// a.把這個T當做ISports
// b.就只能傳遞ISports這個接口或者實現這個接口的類
// c.就可以增加功能,也可以獲取新的功能。
public static void ShowInterface<T>(T tValue) where T : ISports
{
tValue.run();
}
//引用類型約束
// a.就只能傳遞類型進來
public static void ShowClass<T>(T tValue) where T : class
{
}
//值類型約束
// a.只能傳遞值類型進來
public static void ShowValue<T>(T tValue) where T : struct
{
}
//無參數構造函數約束
//
public static void ShowNew<T>(T tValue) where T : new()
{
T t= new T();
}
//枚舉約束
public static void ShowEnum<T>(T tValue) where T : Enum
{
}
七、泛型緩存---泛型類
泛型緩存可以根據不同的類型生成一個新的類的副本;生成無數個副本;
public class GenericCacheTest
{
public static void Show()
{
//普通緩存
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine(DictionaryCache.GetCache<int>()); //GenericCacheInt
Thread.Sleep(10);
Console.WriteLine(DictionaryCache.GetCache<long>());// GenericCachelong
Thread.Sleep(10);
Console.WriteLine(DictionaryCache.GetCache<DateTime>());
Thread.Sleep(10);
Console.WriteLine(DictionaryCache.GetCache<string>());
Thread.Sleep(10);
Console.WriteLine(DictionaryCache.GetCache<GenericCacheTest>());
Thread.Sleep(10);
}
}
//泛型緩存
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine(GenericCache<int>.GetCache()); //GenericCacheInt
Thread.Sleep(10);
Console.WriteLine(GenericCache<long>.GetCache());// GenericCachelong
Thread.Sleep(10);
Console.WriteLine(GenericCache<DateTime>.GetCache());
Thread.Sleep(10);
Console.WriteLine(GenericCache<string>.GetCache());
Thread.Sleep(10);
Console.WriteLine(GenericCache<GenericCacheTest>.GetCache());
Thread.Sleep(10);
}
}
}
}
/// <summary>
/// 字典緩存:靜態屬性常駐內存
/// </summary>
public class DictionaryCache
{
private static Dictionary<Type, string> _TypeTimeDictionary = null;
//靜態構造函數在整個進程中,執行且只執行一次;
static DictionaryCache()
{
Console.WriteLine("This is DictionaryCache 靜態構造函數");
_TypeTimeDictionary = new Dictionary<Type, string>();
}
public static string GetCache<T>()
{
Type type = typeof(T);
if (!_TypeTimeDictionary.ContainsKey(type))
{
_TypeTimeDictionary[type] = $"{typeof(T).FullName}_{DateTime.Now.ToString("yyyyMMddHHmmss.fff")}";
}
return _TypeTimeDictionary[type];
}
}
/// <summary>
///泛型緩存:
/// </summary>
/// <typeparam name="T"></typeparam>
public class GenericCache<T>
{
static GenericCache()
{
Console.WriteLine("This is GenericCache 靜態構造函數");
//_TypeTime = string.Format("{0}_{1}", typeof(T).FullName, DateTime.Now.ToString("yyyyMMddHHmmss.fff"));
_TypeTime = $"{typeof(T).FullName}_{DateTime.Now.ToString("yyyyMMddHHmmss.fff")}";
}
private static string _TypeTime = "";
public static string GetCache()
{
return _TypeTime;
}
}
七、泛型的協變和逆變
比如我們有一個動物類以及它的子類貓類
public class Animal
{
public int Id { get; set; }
}
public class Cat : Animal {
public string Name { get; set; }
}
我們可以使用這樣的代碼
//任何子類都可以使用父類來聲明
Animal animal1 = new Cat();
//不一定能用子類來聲明父類
Cat cat2 = new Animal();
我們還會遇到這樣的問題
一只貓是一堆動物
一堆貓卻不是一堆動物---從口語上來說,有點不符合人類的邏輯的思維。
List<Animal> animals = new List<Animal>();
//報錯
List<Animal> animals2 = new List<Cat>();
為什么?---二類沒有父子級關系;當然不能替換;這是C#語法所決定的。
泛型存在不友好,不協調的地方;
這樣就引入我們的協變和逆變;
協變逆變 只針對于泛型接口和泛型委托
//協變 就可以讓右邊使用子類,能讓左邊用父類
//out:修飾類型參數:就可以讓右邊用子類,能讓左邊用父類
IEnumerable<Animal> animals1=new List<Animal>();
IEnumerable<Animal> animals3= new List<Cat>();
協變: Out類型參數只能做返回值,不能做參數
逆變In 只能做參數,不能做返回值
public interface ICustomerListIn<in T>
{
void show(T t);
}
public class CustomerListIn<T>:ICustomerListIn<T>
{
public void show(T t)
{
}
}
逆變的代碼例子
//逆變:就可以讓右邊用父類;左邊用子類;
ICustomerListIn<Cat> customerListIn = new CustomerListIn<Animal>();
ICustomerListIn<Cat> customerListIn1 = new CustomerListIn<Cat>();
為什么要有協變和逆變?
如果沒有協變和逆變會怎么樣?
public interface ICustomerList<T> {
T Get();
void show(T t);
}
public class CustomerList<T>:ICustomerList<T>
{
public T Get()
{
return default(T);
}
public void show(T t)
{
}
}
}
ICustomerList<Animal> customerList=new CustomerList<Animal>();
ICustomerList<Animal> customerList1 = new CustomerList<Cat>();

泛型的協變和逆變更像是一種高級約束,是為規避
- 把子類做參數,卻把父類當參數傳入;
- 把子類做返回值,卻在返回的時候,返回了一個父類。

浙公網安備 33010602011771號