1. 基本了解
1.1 什么是泛型?
字面意思:不確定的類型
泛型常用:泛型方法,泛型類,泛型接口,泛型委托
1.2 泛型 T(熟悉)
T
的作用,其實就是一個通用的容器,制造它的人開始不指定它是用來裝什么的,而使用者在使用它的時候要告訴這個容器准備用來裝什么,容器知道了用來裝什么之后,后面所有存入操作,它都要檢查一下你放的東西是不是開始指定的東西類型
所謂泛型,即通過參數化類型來實現在同一份代碼上操作多種數據類型
泛型允許您延遲編寫類或方法中的編程元素的數據類型的規范,直到實際在程序中使用它的時候,換句話說,泛型允許您編寫一個可以與任何數據類型一起工作的類或方法
泛型編程是一種編程范式,它利用“參數化類型”將類型抽象化,從而實現更為靈活的復用。在定義泛型類時,在對客戶端代碼能夠在實例化類時,可以用類型參數的類型種類施加限制
原理:泛型的使用是來源於c#2.0新出的規則和框架的升級,對原生需求的變更,泛型不是語法糖,是應對數據類型在傳遞參數的時候解決多代碼冗余問題,減少代碼的重復和可維護性。泛型的協變與逆變和泛型約束在c#4.0出現,解決c#出現的代碼父類繼承問題
1.3 設計思想
泛型的思想表現了一個很重要的架構思想: 延遲思想,推遲一切可以推遲的,使程序有更多的靈活性和擴展性,用來解決,方法中是相同的操作,但是傳入參數是不同類型的問題(例舉)
1.4 應用場景
類型不明確時:自定義對象的時候,如果我們會定義很多類似的對象,之后參數類型不同,那么我們此時可以考慮在定義對象的時候使用泛型
定義變量,定義方法的參數,定義方法的返回值
示例:返回結果
public class Result<T>
{
public int code { get; set; }
public List<T> date { get; set; }
}
Result<A> result_a = new Result<A>() { code = 200, date = new List<A>() };
Result<B> result_b = new Result<B>() { code = 200, date = new List<B>() };
1.5 裝箱拆箱(了解)
在沒有泛型之前,用 object
類型也可以實現相同操作,但是會有些性能損耗及類型安全問題
說明
簡單來說,裝箱是將值類型轉換為引用類型 ;拆箱是將引用類型轉換為值類型
裝箱:用於在垃圾回收堆中存儲值類型。裝箱是值類型到 object
類型或到此值類型所實現的任何接口類型的隱式轉換
拆箱:從 object
類型到值類型或從接口類型到實現該接口的值類型的顯式轉換
c#類型
C#中值類型和引用類型的最終基類都是Object
類型(它本身是一個引用類型)。也就是說,值類型也可以當做引用類型來處理。而這種機制的底層處理就是通過裝箱和拆箱的方式來進行,利用裝箱和拆箱功能,可通過允許值類型的任何值與Object
類型的值相互轉換,將值類型與引用類型鏈接起來
發生場景
一種最普通的場景是,調用一個含類型為Object
的參數的方法,該Object
可支持任意為型,以便通用。當你需要將一個值類型(如Int32
)傳入時,需要裝箱。
另一種用法是,一個非泛型的容器,同樣是為了保證通用,而將元素類型定義為Object
。於是,要將值類型數據加入容器時,需要裝箱
裝箱和拆箱的內部操作
.NET中數據類型划分為值類型和引用類型,與此對應,內存分配被分成了兩種方式,一為棧,二為堆(托管堆)
值類型只會在棧中分配,引用類型分配內存與托管堆(托管堆對應於垃圾回收)
2. 泛型方法
泛型方法可以定義特定於其執行范圍的泛型參數
2.1 定義泛型方法
public class MyClass
{
// 指定MyMethod方法用以執行類型為X的參數
public void MyMethod<X>(X x)
{
//
}
//此方法也可不指定方法參數
public void MyMethod<X>()
{
//
}
}
2.2 調用泛型方法
MyClass mycls = new MyClass();
mycls.MyMethod<int>(3);
3. 泛型類
類級別泛型參數的所有約束都必須在類作用范圍中定義
3.1 定義泛型類
簡單定義
public class MyC<T>
{
...
}
public class MyC<T>
{
public T Get(){...}
public void Show(T t){...}
}
普通類繼承泛型類
public class MyClass<T>:MyC<T>
{
...
}
泛型類繼承泛型類,繼承的泛型類型必須是可推斷的(與子類一致或者一個具體的類型)
public class MyClass2<T>:MyC<int>
{
...
}
3.2 其它示例
示例一
public class MyC<T> where T:new()
{
public void Show<T>(){T entity}
}
public class MyClass<T> where T:IComparable<T>
{
public void MyMethod<X>(X x,T t)
{
//
}
}
4. 泛型接口
4.1 定義泛型接口
簡單定義
public interface MyInterface<T>
{
...
}
public interface MyInterface<T>
{
T Get();
void Show(T t);
}
泛型接口繼承接口
public interface MyInterface2<T>:MyInterface<T>
{
...
}
5. 泛型委托
在某個類中定義的委托可以使用該類的泛型參數
5.1 定義泛型委托
示例一
public class MyClass<T>
{
public delegate void GenericDelegate(T t);
public void SomeMethod(T t)
{
}
}
5.2 使用泛型委托
示例一:同定義示例一
public GenericMethodDemo()
{
MyClass<int> obj = new MyClass<int>();
MyClass<int>.GenericDelegate del;
del = new MyClass<int>.GenericDelegate(obj.SomeMethod);
del(3);
}
6. 泛型約束
6.1 類型安全問題
show
方法若使用 object
類型參數,雖然編譯器中沒有報錯,但是在運行中會出現類型轉換失敗問題,原因是類型 C
並沒有繼承 A
父類,此處就引發了類型錯誤問題,而泛型約束就是解決類型安全問題
namespace t1
{
class Program
{
static void Main(string[] args)
{
A obja = new A() { id = 1, name = "a" };
B objb = new B() { id = 2, name = "b" };
C objc = new C() { id = 3, name = "C" };
try
{
Show(obja);
Show(objb);
Show(objc);
}
catch (Exception ex)
{
}
}
static void Show(object oval)
{
A obj = (A)oval;
Console.WriteLine(obj.id);
}
}
public class A
{
public int id { get; set; }
public string name { get; set; }
}
public class B : A
{
}
public class C
{
public int id { get; set; }
public string name { get; set; }
}
}
6.2 常用約束列表
約束 | 說明 |
---|---|
where T:基類名 | 類型參數必須是指定的基類或派生自指定的基類 |
where T:接口名稱 | 類型參數必須是指定的接口或實現指定的接口 |
where T:class | 類型參數必須是引用類型,包括任何類、接口、委托或數組類型 |
where T:struct | 類型參數必須是值類型,可以指定除 Nullable 以外的任何值類型 |
where T:new() | 類型參數必須具有無參數的公共構造函數,與其他約束同使用時,必須最后指定 |
6.3 常用示例
示例一:接口約束|派生約束
// 1.常見
public class MyGenericClass<T> where T:IComparable { }
// 2.約束放在類的實際派生之后
public class B { }
public class MyClass6<T> : B where T : IComparable { }
// 3.可以繼承一個基類和多個接口,且基類在接口前面
public class B { }
public class MyClass7<T> where T : B, IComparable, ICloneable { }
示例二:引用類型,值類型約束
public class c<T> where T:class
public class MyClassy<T, U> where T : class where U : struct
{
}
構造函數約束
以使用 new
運算符創建類型參數的實例;但類型參數為此必須受構造函數約束 new()
的約束。new()
約束可以讓編譯器知道:提供的任何類型參數都必須具有可訪問的無參數(或默認)構造函數。new()
約束出現在 where 子句的最后
// 1.常見的
public class MyClass8<T> where T : new() { }
// 2.可以將構造函數約束和派生約束組合起來,前提是構造函數約束出現在約束列表的最后
public class MyClass8<T> where T : IComparable, new() { }
7. 泛型緩存
泛型緩存,使用泛型類+靜態字段,根據不同類型的“T
”會被即時編譯為不同的類型從而實現緩存
個人理解,T
的作用是一個類型模板,靜態字段本身就是線程唯一的,使用泛型時,指定類型從而生成這個類型的副本,從而這個類型中的靜態內容也就生成了一份
7.1 示例一:簡單示例
定義泛型類,緩存內容使用靜態變量並在第一次執行(使用)時緩存此類型(T
)內容
public class GenericClass<T>
{
private static string GenericCache = null;
static GenericClass() // 靜態構造函數,在泛型類第一次傳入具體的類型進來的時候,執行
{
GenericCache = $"{typeof(T).Name}-{DateTime.Now.ToLocalTime()}";
}
public static string GetData()
{
return GenericCache;
}
}
測試使用,測試不同類型,或同一類型多次調用
static void Main(string[] args)
{
for (int i = 0; i < 3; i++)
{
Console.WriteLine(GenericClass<string>.GetData());
Thread.Sleep(1000);
Console.WriteLine(GenericClass<int>.GetData());
Thread.Sleep(1000);
}
}
7.2 示例一:簡單簡版
public class GenericClass<T>
{
// 定義為靜態只讀變量
public static readonly string GenericCache = null;
static GenericClass() // 靜態構造函數,在泛型類第一次傳入具體的類型進來的時候,執行
{
GenericCache = $"{typeof(T).Name}\t{DateTime.Now.ToLocalTime()}";
}
}
static void Main(string[] args)
{
for (int i = 0; i < 3; i++)
{
// 只能獲取,不能賦值
Console.WriteLine(GenericClass<string>.GenericCache);
Thread.Sleep(1000);
Console.WriteLine(GenericClass<int>.GenericCache);
Thread.Sleep(1000);
}
}
7.3 示例二:實際示例
定義泛型類,用於實現緩存指定SQL語句
public class GenericSql<T> where T : class
{
public static readonly string FindSql = null;
public static readonly string DeleteSql = null;
public static readonly string FindAllSql = null;
public static readonly string UpdateSql = null;
static GenericSql()
{
Type type = typeof(T);
var props = type.GetProperties().Select(x => $"{x.Name}");
FindSql = $"select {string.Join(",", props.Select(x => $"[{x}]"))} from [{type.Name}] where id=@id";
DeleteSql = $"delete from [{type.Name}] where id=@id";
FindAllSql = $"select {string.Join(",", props.Select(x => $"[{x}]"))} from [{type.Name}]";
UpdateSql = $"update [{type.Name}] set {string.Join(",", props.Where(x => !x.Equals("id")).Select(x => $"[{x}]=@{x}"))} where id=@id";
}
}
測試使用,使用兩個實體類測試
public class Role
{
public int rid { get; set; }
public string rname { get; set; }
}
public class User
{
public int uid { get; set; }
public string uname { get; set; }
}
static void Main(string[] args)
{
for (int i = 0; i < 3; i++)
{
Console.WriteLine(GenericSql<Role>.FindSql);
Console.WriteLine(GenericSql<User>.FindSql);
Console.WriteLine(GenericSql<Role>.DeleteSql);
Console.WriteLine(GenericSql<User>.DeleteSql);
Console.WriteLine(GenericSql<Role>.FindAllSql);
Console.WriteLine(GenericSql<User>.FindAllSql);
Console.WriteLine(GenericSql<Role>.UpdateSql);
Console.WriteLine(GenericSql<User>.UpdateSql);
}
}
8. 協變逆變(擴展)
協變和逆變只有在泛型接口,泛型委托中才有,協變逆變也可以組合使用
8.1 使用問題
// 鳥類
public class Bird
{
public int id { get; set; }
}
// 麻雀類
public class Sparrow:Bird
{
public string name { get; set; }
}
class Program
{
static void Main(string[] args)
{
// 麻雀也屬於鳥類
Bird bird1 = new Bird();
Bird bird2 = new Sparrow();
// 從人類語言上來說,一組麻雀也是一組鳥類
// 但是在程序中,List<Bird> 是一個新的類,與 List<Sparrow> 無父子關系
// List<Bird> list = new List<Sparrow>(); 報錯
}
}
8.2 協變
協變:使用 out
修飾類型參數,且類型參數只能用作返回值,不可用於輸入參數,使得子類可在右邊
namespace t2
{
// 鳥類
public class Bird
{
public int id { get; set; }
}
// 麻雀類
public class Sparrow : Bird
{
public string name { get; set; }
}
// 自定義協變
public interface ICustomerListOut<out T>
{
T Get();
//Show(T t);
}
public class CustomerListOut<T> : ICustomerListOut<T>
{
public T Get()
{
return default(T);
}
//public void Show(T t) { }
}
class Program
{
static void Main(string[] args)
{
// 協變
IEnumerable<Bird> birds1 = new List<Sparrow>();
// 自定義
ICustomerListOut<Bird> birds2 = new CustomerListOut<Sparrow>();
}
}
}
8.3 逆變
逆變:使用 in
修飾類型參數,且類型參數只能用作輸入參數,不可用於輸入參數返回值,使得父類可在右邊
namespace t2
{
// 鳥類
public class Bird
{
public int id { get; set; }
}
// 麻雀類
public class Sparrow : Bird
{
public string name { get; set; }
}
public interface ICustomerListIn<in T>
{
// T Get();
void Show(T t);
}
public class CustomerListIn<T> : ICustomerListIn<T>
{
//public T Get()
//{
// return default(T);
//}
public void Show(T t) { ... }
}
class Program
{
static void Main(string[] args)
{
// 逆變
ICustomerListIn<Sparrow> sparrow1 = new CustomerListIn<Bird>();
}
}
}
8.4 個人理解(不做參考)
協變:子類向父類轉換
逆變:父類向子類轉換