C# 基礎知識系列- 5 反射和泛型

前言

爲何要把反射和泛型放在一塊兒講呢,這裏是處於我的對C#的一個很棒的觀感,由於C#的反射是能夠獲取泛型裏的元素的,而不像Java一個讓我比較難受的地方就是Java的泛型實際編譯的時候會擦除類型信息。
那麼問題來了,什麼是泛型,什麼又是反射呢?程序員

泛型

請原諒我先介紹泛型,由於沒有泛型基礎直接介紹反射是不完整的,就好比說你辛辛苦苦拿到一個類的反射信息,等用的時候才發現結果這是一個泛型類,那還得解析這個類的泛型的信息,這時候就必須先有一個泛型的基礎。
那麼什麼是泛型呢,先看看百度百科給的定義:c#

泛型是程序設計語言的一種特性。容許程序員在強類型程序設計語言中編寫代碼時定義一些可變部分,那些部分在使用前必須做出指明。各類程序設計語言和其編譯器、運行環境對泛型的支持均不同。將類型參數化以達到代碼複用提升軟件開發工做效率的一種數據類型。泛型類是引用類型,是堆對象,主要是引入了類型參數這個概念。數組

額,說實話哈,有一部分我沒看懂他寫的是啥。根據個人理解,泛型就是模板類裏套的參數。就比如咱們從網上找到一個好看的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 ,在下面的屬性、字段、方法的參數和方法的返回值均可以使用這個佔位符,約定類型一致。設計

泛型的接口和泛型類是一致的,只不過接口沒有方法的實現內容也就是方法體而已。code

泛型類的使用

// 繼續上面的代碼
Template<int> temp = new Template<int>();
temp.SetTemplate(10);
int ten = temp.GetTemplate();

使用泛型類和普通類不一樣的地方就是,泛型類告訴編譯器你要傳遞的類型。使用<> 作標記,中間寫類型,表示這是一個泛型爲XXX的泛型類。一般與其餘語言不一樣的地方是,C#的泛型支持全部類型,意思就是在沒有額外聲明的時候,可使用任意類型做爲泛型參數傳遞。對象

泛型方法

C#也能夠聲明一個方法爲泛型方法,方法的泛型聲明是聲明在方法名的後面,參數列表的前方。blog

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#中常見的有以下兩個方法:

  1. 使用typeof 關鍵字
Type personType = typeof(Person);
  1. 經過對象,使用GetType 方法
Person person = new Person();
Type personType = person.GetType();

若是咱們在編寫程序的時候,知道要獲取什麼類的Type對象的話,建議使用typeof獲取。若是咱們只有一個對象,須要經過這個對象進行操做的話,那麼最好使用GetType來獲取。

如今咱們獲取到了一個Person類的Type對象,能夠用來作什麼呢?

Type對象的用處

  1. 獲取類名:personType.Name
  2. 獲取全部屬性:personType.GetProperties()
  3. 獲取全部方法:personType.GetMethods()
  4. 獲取全部構造函數: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});

這時候一個簡單的反射介紹就到這裏了,反射這裏還有一大篇的內容要將。這部分我會放到基礎篇完結以後再作一個統一介紹的。不過先道個歉,沒介紹泛型在反射的應用。

注:代碼裏映射的王先生是我一個故人,最近與他有一些糾紛。
更多內容煩請關注個人博客

file

相關文章
相關標籤/搜索