前言
為什么要把反射和泛型放在一起講呢,這里是處于個人對C#的一個很棒的觀感,因為C#的反射是可以獲取泛型里的元素的,而不像Java一個讓我比較難受的地方就是Java的泛型實際編譯的時候會擦除型別資訊,
那么問題來了,什么是泛型,什么又是反射呢?
泛型
請原諒我先介紹泛型,因為沒有泛型基礎直接介紹反射是不完整的,就比如說你辛辛苦苦拿到一個類的反射資訊,等用的時候才發現結果這是一個泛型類,那還得決議這個類的泛型的資訊,這時候就必須先有一個泛型的基礎,
那么什么是泛型呢,先看看百度百科給的定義:
泛型是程式設計語言的一種特性,允許程式員在強型別程式設計語言中撰寫代碼時定義一些可變部分,那些部分在使用前必須作出指明,各種程式設計語言和其編譯器、運行環境對泛型的支持均不一樣,將型別引數化以達到代碼復用提高軟體開發作業效率的一種資料型別,泛型類是參考型別,是堆物件,主要是引入了型別引數這個概念,
額,說實話哈,有一部分我沒看懂他寫的是啥,根據我的理解,泛型就是模板類里套的引數,就好比我們從網上找到一個好看的PPT模板,我們在寫PPT的時候根據我們的主題套用這個模板,然后寫出一個很好看的PPT,被老板表揚升職加薪,嗯,事實上用好了泛型也會升職加薪,
泛型說的籠統一些就是型別引數化的程序,我們之前介紹的List就是一個泛型類,泛型分泛型類/介面和泛型方法,泛型類和泛型介面可以看做是一種,因為它的泛型引數是用在整個結構體里面的(注意不是結構,struct);泛型方法又有引數泛型和回傳值泛型兩種,
宣告一個泛型類/介面
public class Template<T>
{
private T data;
public void SetTemplate(T temp)
{
data = https://www.cnblogs.com/c7jie/p/temp;
}
public T GetTemplate()
{
return data;
}
}
上述示例是一個簡單的泛型類,體現了泛型類的特點,在宣告類的時候,宣告一個泛型占位符T ,在下面的屬性、欄位、方法的引數和方法的回傳值都可以使用這個占位符,約定型別一致,
泛型的介面和泛型類是一致的,只不過介面沒有方法的實作內容也就是方法體而已,
泛型類的使用
// 繼續上面的代碼
Template<int> temp = new Template<int>();
temp.SetTemplate(10);
int ten = temp.GetTemplate();
使用泛型類和普通類不同的地方就是,泛型類告訴編譯器你要傳遞的型別,使用<> 做標記,中間寫型別,表示這是一個泛型為XXX的泛型類,通常與其他語言不同的地方是,C#的泛型支持所有型別,意思就是在沒有額外宣告的時候,可以使用任意型別作為泛型引數傳遞,
泛型方法
C#也可以宣告一個方法為泛型方法,方法的泛型宣告是宣告在方法名的后面,引數串列的前方,
public void TemplateMethod<T>(T arg);
public T TemplateMethod1<T>();
public T TemplateMethod2<T>(T arg);
上述三個都是合規的泛型方法宣告,泛型可以是引數,也可以是回傳值,還能既是回傳值又是引數,
那么問題來了,多個泛型引數該怎么宣告?
如下:
public T2 TemplateMothod3<T1,T2>(T1 arg);
public T3 TemplateMothod4<T1,T2,T3>(T1 arg,T2 arg2);
在兩個尖括號中間放入多個泛型,然后用逗號隔開,與引數串列和回傳值的型別一一對應,
泛型方法的使用
TemplateMethod(10);// 方式 1
int it = TemplateMethod1<int>();// 方式 2
由于篇幅和時間的關系(主要是我寫這篇的時候時間有點晚了,,)就不對之前所有的方法進行演示了,
這里簡單介紹一下泛型方法的使用:
- 方式1 隱藏了一個泛型引數,這是因為如果泛型是引數的話,c#會根據引數的型別自動決議對應的泛型型別是什么,方式1 等同于
TemplateMethod<int>(10);, - 方式2 當泛型引數是回傳值時,必須告知具體的泛型型別,
泛型約束和泛型標記
約束
在實際開發程序中,我們會對一些泛型類的泛型引數進行型別約束,那么泛型約束應該怎么寫呢,看示例:
public void Demo<T>(T arg) where T : 約束內容
public void Demo<T,P>(T arg,P arg1) where T: 約束內容 where P:約束內容
如果對多個引數進行約束,就寫多個where,
泛型的約束有一下幾種:
- class 表示這是個參考型別
- new() 表示必須有一個無參建構式
- struct 表示是個結構體
- 具體的類名或介面名 表示這個引數必須是這個類的子類或介面的實作類
泛型標記
在C#里有個很有意思的地方,那就是泛型標記,
泛型支持 in/out作為占位符T的前置標記,那這兩個標記是什么意義呢,in表示這個型別引數只能作為引數串列的型別進行傳遞,out表示這是一個回傳值的型別,示例如下:
public T2 Demo<in T1,out T2>(T1 t1);
類和方法的標記大同小易,基本上是一致的,
反射
反射在很多地方都有著使用,這里先簡單的介紹一下C#中的反射相關內容,因為細講的話會涉及到很多東西而且還需要很多前置概念,不過在自己寫框架之前不需要涉及到太多反射的內容,
反射,英文名 reflect,簡單的介紹就是將型別物件化,然后操作這個物件的技術,
我們先創建一個示例類:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Person()
{
Name = "小李";
Age = 24;
}
public Person(string name, int age)
{
Name = name;
Age = age;
}
public string SayHi()
{
return "你好,我叫" + Name + "我的年紀是 " + Age;
}
}
獲取一個型別物件
首先需要注意的一個類:Type,這個類是反射技術里的基石,甚至可以說是核心,表示一個類的型別資訊,
那么,我們該如何獲取型別物件呢?在C#中常見的有如下兩個方法:
- 使用
typeof關鍵字
Type personType = typeof(Person);
- 通過物件,使用
GetType方法
Person person = new Person();
Type personType = person.GetType();
如果我們在撰寫程式的時候,知道要獲取什么類的Type物件的話,建議使用typeof獲取,如果我們只有一個物件,需要通過這個物件進行操作的話,那么最好使用GetType來獲取,
現在我們獲取到了一個Person類的Type物件,可以用來做什么呢?
Type物件的用處
- 獲取類名:
personType.Name - 獲取所有屬性:
personType.GetProperties() - 獲取所有方法:
personType.GetMethods() - 獲取所有建構式:
personType.GetConstructors()
現在我們一一介紹一下這四種寫法:
第一條:顧名思義,獲取到的結果是Person 這個值,
第二條:該方法會回傳一個型別為PropertyInfo[] 的陣列,這個陣列里包含著所有使用public宣告的屬性,當然也可以通過指定的屬性名獲取屬性物件:personType.GetProperty("Name") 這里會獲取到Person類的Name屬性,
第三條: 獲取該類所有public的方法,并將其封裝成一組型別是MethodInfo的物件陣列,同理,也可以根據方法名進行檢索:personType.GetMethod("SayHi") ,就能獲取對應的SayHi方法,不過,如果有同名方法的話,就可能會出現獲取到的方法不是你想要的了,嗯,這部分會放到精講反射的時候再來細說,
第四條: 獲取建構式,回傳的是一個型別是ConstructorInfo的陣列,表示所有的構造方法,不過可惜的是,沒有根據名字檢索的方法了,因為構造方法就一個名,
使用PropertyInfo動態操作一個物件的屬性值
我們通過上一小節獲取到了一個類的屬性PropertyInfo,現在可以利用這個屬性進行后續的操作:
Person person = new Person();
Type personType = person.GetType();
PropertyInfo prop = personType.GetProperty("Name");//獲取Name屬性
Object value = https://www.cnblogs.com/c7jie/p/prop.GetValue(person);// 獲取 物件 person 的Name屬性值
prop.SetValue(person, "wangyipeng");// 為物件 person的Name屬性設定值為 wangyipeng
需要注意的是:
如果 類的屬性只有get,那么在呼叫SetValue時會報錯,可能要問了,我們知道是有set,但是程式怎么判斷呢?通過prop.CanWrite 的值進行判斷,如果值是true則表明這個屬性可以寫入值,否則不能,
同理,可以很輕易的聯想到如果只有set,那么GetValue也會報錯,與之相對應的就是prop.CanRead屬性了,
使用MethodInfo手動執行一個物件的方法
首先,獲得到一個物件里的某一個方法:
Person person = new Person();
Type personType = person.GetType();
MethodInfo method = personType.GetMethod("SayHi");
現在獲取到了 方法物件,該怎么執行呢?
MethodInfo有一個Invoke方法,這個方法有兩個多載版本,其中有一個是:Invoke(object obj, object[] parameters),第一個引數是要執行的方法所屬的物件,后面的陣列引數是對應方法的引數串列,如果為空則填null即可,該方法有個回傳值,型別是object,如果方法是沒有回傳值的方法,那么Invoke的回傳值就是null,
通過反射獲取一個物件
通過反射獲取一個類的型別物件有幾種方式,先介紹一個不用型別的方式:
Person p = Activator.CreateInstance<Person>();
這種方式有一個要求,Person必須有一個無參的建構式,
第二種方式:
Type personType = typeof(Person);
object p = Activator.CreateInstance(personType);//使用無參建構式
p = Activator.CreateInstance(personType, "小王", 19);//使用Person(string,int)這個建構式
當需要傳遞引數的時候,引數型別必須與對應的建構式一一對應,如果順序變了,可能會出現找不到對應類的問題,
第三種:
//types 是引數串列的引數型別集合,順序與實際引數順序一致
ConstructorInfo cons = personType.GetConstructor(Type[] types);
/*
實際上應該是這個呼叫方
ConstructorInfo cos = personType.GetConstructor(new[]{ typeof(string), typeof(int)});
*/
object person = cos.Invoke(new object[] {"王先生", 19});
這時候一個簡單的反射介紹就到這里了,反射這里還有一大篇的內容要將,這部分我會放到基礎篇完結之后再做一個統一介紹的,不過先道個歉,沒介紹泛型在反射的應用,
注:代碼里映射的王先生是我一個故人,最近與他有一些糾紛,
更多內容煩請關注我的博客

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/56679.html
標籤:C#
上一篇:String.Split分隔字串
