前言 為什麼要把反射和泛型放在一起講呢,這裡是處於個人對C 的一個很棒的觀感,因為C 的反射是可以獲取泛型里的元素的,而不像Java一個讓我比較難受的地方就是Java的泛型實際編譯的時候會擦除類型信息。 那麼問題來了,什麼是泛型,什麼又是反射呢? 泛型 請原諒我先介紹泛型,因為沒有泛型基礎直接介紹反 ...
前言
為什麼要把反射和泛型放在一起講呢,這裡是處於個人對C#的一個很棒的觀感,因為C#的反射是可以獲取泛型里的元素的,而不像Java一個讓我比較難受的地方就是Java的泛型實際編譯的時候會擦除類型信息。
那麼問題來了,什麼是泛型,什麼又是反射呢?
泛型
請原諒我先介紹泛型,因為沒有泛型基礎直接介紹反射是不完整的,就比如說你辛辛苦苦拿到一個類的反射信息,等用的時候才發現結果這是一個泛型類,那還得解析這個類的泛型的信息,這時候就必須先有一個泛型的基礎。
那麼什麼是泛型呢,先看看百度百科給的定義:
泛型是程式設計語言的一種特性。允許程式員在強類型程式設計語言中編寫代碼時定義一些可變部分,那些部分在使用前必須作出指明。各種程式設計語言和其編譯器、運行環境對泛型的支持均不一樣。將類型參數化以達到代碼復用提高軟體開發工作效率的一種數據類型。泛型類是引用類型,是堆對象,主要是引入了類型參數這個概念。
額,說實話哈,有一部分我沒看懂他寫的是啥。根據我的理解,泛型就是模板類里套的參數。就好比我們從網上找到一個好看的PPT模板,我們在寫PPT的時候根據我們的主題套用這個模板,然後寫出一個很好看的PPT,被老闆表揚升職加薪。嗯,事實上用好了泛型也會升職加薪。
泛型說的籠統一些就是類型參數化的過程,我們之前介紹的List就是一個泛型類。泛型分泛型類/介面和泛型方法。泛型類和泛型介面可以看做是一種,因為它的泛型參數是用在整個結構體裡面的(註意不是結構,struct);泛型方法又有參數泛型和返回值泛型兩種。
聲明一個泛型類/介面
public class Template<T>
{
private T data;
public void SetTemplate(T temp)
{
data = 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 = 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});
這時候一個簡單的反射介紹就到這裡了,反射這裡還有一大篇的內容要將。這部分我會放到基礎篇完結之後再做一個統一介紹的。不過先道個歉,沒介紹泛型在反射的應用。
註:代碼里映射的王先生是我一個故人,最近與他有一些糾紛。
更多內容煩請關註我的博客