元數(shù)據(jù)是在程序編譯過程中創(chuàng)建并嵌入到程序集中者蠕,利用反射技術(shù)可以讀取程序的元數(shù)據(jù)各谚,可以做以下操作:
- 獲取類型的成員省核,包含變量和函數(shù)
- 實(shí)例化一個對象
- 執(zhí)行對象的成員函數(shù)
- 獲取類型的信息
- 獲取程序集的信息
- 獲取自定義特征
- 創(chuàng)建和編譯新程序集
自定義特征
預(yù)定義的許多特征墩虹,C#編譯器都是支持的,也就是說授嘀,編譯器可以在編譯階段識別并解讀這些特征物咳,然后根據(jù)這些特征的預(yù)定義規(guī)則去定制編譯過程。而用戶自定義的特征蹄皱,編譯器明顯是不能識別的览闰,不過編譯器會將其作為元數(shù)據(jù)加在對應(yīng)的元素上芯肤,嵌入到程序集中。特征轉(zhuǎn)化為元數(shù)據(jù)后压鉴,我們就可以利用反射讀取這些元數(shù)據(jù)纷妆,使程序在運(yùn)行期間做出決策。說的通俗一點(diǎn)晴弃,自定義特征可能影響程序的邏輯走向。
//自定義一個特征逊拍,名稱為FieldNameAttribute,可以作用于屬性元素上上鞠,有一個必填的屬性為name
//AttributeTargets是個枚舉值,表示特征能用于什么元素上芯丧,這里設(shè)置為可以用于屬性和字段上
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class FieldNameAttribute:Attribute{
public string Name {get; private set;};
public string Scoped {get;set;}
public FieldNameAttribute(string name){
this.Name = name;
}
}
public class Person
{
/*
將FeildNameAttribute加載Name屬性項(xiàng)芍阎,
因?yàn)镕ieldNameAttribute的構(gòu)造函數(shù)中有name參數(shù),
所以這里的“名字”缨恒,并沒有加上name="...",而是直接賦的值
*/
[FeildName("名字",Scpoed ="可選項(xiàng)")]
public string Name { get; set; }
public Person()
{
}
public void foo(){
Attribute info = Attribute.GetCustomAttribute(typeof(Person),typeof(FeildNameAttribute));
FeildNameAttribute attr = (FeildNameAttribute)info;
Console.WriteLine(attr.Name);
Console.WriteLine(attr.Scoped);
}
}
特征類本身會用一個預(yù)定義特征(System.AttributeUsage)來標(biāo)記它是一個特征谴咸。特征類有了這個AttributeUsage特征后,編譯器就會為它做特殊處理骗露。
反射
上面講的自定義特征岭佳,僅僅是為了給反射鋪路。提到反射就必須要知道System.Type類萧锉。Type是訪問元數(shù)據(jù)的主要方式珊随,使用Type提供的方法可以獲得指定類型的信息,比如獲取一個類定義的構(gòu)造函數(shù)柿隙,方法叶洞,屬性,字段禀崖,事件等信息衩辟,甚至還以獲得這個類依賴的模塊和程序集等〔ǜ剑可以使用Type描述的類型有:Class,Value type,Array,Interface,Enumeration,Delegate,generic type(由泛型創(chuàng)建的對象)艺晴。獲取一個Type對象有以下3種方法:
- typeof()運(yùn)算符,typeof運(yùn)算符中加要獲取的類型的類型名掸屡,如:typeof(string)
- Object.GetType(),這個方法是通過實(shí)例對象獲取該對象的Type類型财饥,如 var a = 0; a.GetType();
- Type.GetType(),這個是Type類型提供的靜態(tài)方法折晦,Type type = Type.GetType("Demo.Person");
這里需要注意的是Type是abstract類型的钥星,所以不能被實(shí)例化,我們通過上面的方式獲得的Type實(shí)例满着,其實(shí)是程序在運(yùn)行時根據(jù)具體的類型生成的Type派生類的實(shí)例谦炒。
Type實(shí)例獲取的反射數(shù)據(jù)大致分為兩類贯莺,第一類是描述類型本身的屬性,比如類型的名稱Name宁改,類型的命名空間Namespace缕探,完全限定名FullName等。第二類是類型內(nèi)的元素还蹲,包括構(gòu)造函數(shù)爹耗,方法,屬性谜喊,字段等信息潭兽。
返回的對象類型 | 方法 | 描述 |
---|---|---|
MemberInfo | GetMember() | 獲取成員,包括屬性斗遏,字段山卦,方法,事件等 |
ConstructorInfo | GetConstructor() | 構(gòu)造函數(shù) |
MethodInfo | GetMethod() | 方法 |
EnventInfo | GetEvnet() | 事件 |
PropertyInfo | GetProperty() | 屬性 |
FieldInfo | GetField() | 字段 |
以上介紹的都是使用反射讀取類型的元數(shù)據(jù)诵次,有了這些元數(shù)據(jù)后就可以實(shí)例化對象并調(diào)用其方法账蓉,以下是一些調(diào)用思路。
//獲取Foo的公共無參構(gòu)造函數(shù)逾一,并實(shí)例化該對象
var foo = typeof(Foo).GetConstructors()
.ToList()
.Where(c => c.IsPublic && c.GetParameters().Length==0)
.First()?.Invoke(null);
//上面的列子可以進(jìn)一步泛化铸本,創(chuàng)建T類型的無參構(gòu)造實(shí)例
/// <summary>
/// 創(chuàng)建T類型的無參構(gòu)造實(shí)例
/// </summary>
public T CreateInstance<T>(){
var result = typeof(T).GetConstructors()
.ToList()
.Where(c => c.IsPublic && c.GetParameters().Length == 0)
.First()?.Invoke(null);
return (T)result;
}
//如果要創(chuàng)建有參的實(shí)例也是可以的,只是要稍微麻煩一點(diǎn)遵堵,需要獲取指定與參數(shù)列表中參數(shù)類型和個數(shù)相同的構(gòu)造函數(shù)归敬,這里代碼未寫
//獲取foo的方法并調(diào)用
typeof(Foo).GetMethod("Bar").Invoke(foo, new object[] { /* params */ });
動態(tài)類型
動態(tài)編程的實(shí)質(zhì)是程序在編譯時,不告訴編譯器要創(chuàng)建什么類型的實(shí)例鄙早,等到程序運(yùn)行時汪茧,動態(tài)的創(chuàng)建特定類型的實(shí)例和調(diào)用該實(shí)例的方法。
dynamic類型使編譯器在編譯期間忽略類型檢查限番,dynamic對象與var關(guān)鍵字不同舱污,var是編譯器在編譯期間反推對象的類型,也就是說弥虐,用var關(guān)鍵字定義的對象時扩灯,編譯后會有明確的數(shù)據(jù)類型的,而dynamic對象編譯后不知道是什么類型霜瘪,它可以在運(yùn)行期間任意的改變類型珠插。
var a = 1;
a = "Hello World";//編譯錯誤,a已經(jīng)是int類型颖对。
dynamic b = 1;
b = "Hello World"; //程序在運(yùn)行時捻撑,動態(tài)的將b的類型修改為string類型