反射入門
MSDN:
ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.2052/cpguide/html/cpcondiscoveringtypeinformationatruntime.htm
提綱:
1、 什麼是反射
2、 命名空間與裝配件的關系
3、 運作期得到類型資訊有什麼用
4、 如何使用反射擷取類型
5、 如何根據類型來動态建立對象
6、 如何擷取方法以及動态調用方法
7、 動态建立委托
1、什麼是反射
Reflection,中文翻譯為反射。
這是.Net中擷取運作時類型資訊的方式,.Net的應用程式由幾個部分:‘程式集(Assembly)’、‘子產品(Module)’、‘類型(class)’組成,而反射提供一種程式設計的方式,讓程式員可以在程式運作期獲得這幾個組成部分的相關資訊,例如:
Assembly類可以獲得正在運作的裝配件資訊,也可以動态的加載裝配件,以及在裝配件中查找類型資訊,并建立該類型的執行個體。
Type類可以獲得對象的類型資訊,此資訊包含對象的所有要素:方法、構造器、屬性等等,通過Type類可以得到這些要素的資訊,并且調用之。
MethodInfo包含方法的資訊,通過這個類可以得到方法的名稱、參數、傳回值等,并且可以調用之。
諸如此類,還有FieldInfo、EventInfo等等,這些類都包含在System.Reflection命名空間下。
2、命名空間與裝配件的關系
很多人對這個概念可能還是很不清晰,對于合格的.Net程式員,有必要對這點進行澄清。
命名空間類似與Java的包,但又不完全等同,因為Java的包必須按照目錄結構來放置,命名空間則不需要。
裝配件是.Net應用程式執行的最小機關,編譯出來的.dll、.exe都是裝配件。
裝配件和命名空間的關系不是一一對應,也不互相包含,一個裝配件裡面可以有多個命名空間,一個命名空間也可以在多個裝配件中存在,這樣說可能有點模糊,舉個例子:
裝配件A:
namespace N1
{
public class AC1 {…}
public class AC2 {…}
}
namespace N2
{
public class AC3 {…}
public class AC4{…}
}
裝配件B:
namespace N1
{
public class BC1 {…}
public class BC2 {…}
}
namespace N2
{
public class BC3 {…}
public class BC4{…}
}
這兩個裝配件中都有N1和N2兩個命名空間,而且各聲明了兩個類,這樣是完全可以的,然後我們在一個應用程式中引用裝配件A,那麼在這個應用程式中,我們能看到N1下面的類為AC1和AC2,N2下面的類為AC3和AC4。
接着我們去掉對A的引用,加上對B的引用,那麼我們在這個應用程式下能看到的N1下面的類變成了BC1和BC2,N2下面也一樣。
如果我們同時引用這兩個裝配件,那麼N1下面我們就能看到四個類:AC1、AC2、BC1和BC2。
到這裡,我們可以清楚一個概念了,命名空間隻是說明一個類型是那個族的,比如有人是漢族、有人是回族;而裝配件表明一個類型住在哪裡,比如有人住在北京、有人住在上海;那麼北京有漢族人,也有回族人,上海有漢族人,也有回族人,這是不沖突的。
上面我們說了,裝配件是一個類型居住的地方,那麼在一個程式中要使用一個類,就必須告訴編譯器這個類住在哪兒,編譯器才能找到它,也就是說必須引用該裝配件。
那麼如果在編寫程式的時候,也許不确定這個類在哪裡,僅僅隻是知道它的名稱,就不能使用了嗎?答案是可以,這就是反射了,就是在程式運作的時候提供該類型的位址,而去找到它。
有興趣的話,接着往下看吧。
3、運作期得到類型資訊有什麼用
有人也許疑問,既然在開發時就能夠寫好代碼,幹嘛還放到運作期去做,不光繁瑣,而且效率也受影響。
這就是個見仁見智的問題了,就跟早綁定和晚綁定一樣,應用到不同的場合。有的人反對晚綁定,理由是損耗效率,但是很多人在享受虛函數帶來的好處的時侯還沒有意識到他已經用上了晚綁定。這個問題說開去,不是三言兩語能講清楚的,是以就點到為止了。
我的看法是,晚綁定能夠帶來很多設計上的便利,合适的使用能夠大大提高程式的複用性和靈活性,但是任何東西都有兩面性,使用的時侯,需要再三衡量。
接着說,運作期得到類型資訊到底有什麼用呢?
還是舉個例子來說明,很多軟體開發者喜歡在自己的軟體中留下一些接口,其他人可以編寫一些插件來擴充軟體的功能,比如我有一個媒體播放器,我希望以後可以很友善的擴充識别的格式,那麼我聲明一個接口:
public interface IMediaFormat
{
string Extension {get;}
Decoder GetDecoder();
}
這個接口中包含一個Extension屬性,這個屬性傳回支援的擴充名,另一個方法傳回一個解碼器的對象(這裡我假設了一個Decoder的類,這個類提供把檔案流解碼的功能,擴充插件可以派生之),通過解碼器對象我就可以解釋檔案流。
那麼我規定所有的解碼插件都必須派生一個解碼器,并且實作這個接口,在GetDecoder方法中傳回解碼器對象,并且将其類型的名稱配置到我的配置檔案裡面。
這樣的話,我就不需要在開發播放器的時侯知道将來擴充的格式的類型,隻需要從配置檔案中擷取現在所有解碼器的類型名稱,而動态的建立媒體格式的對象,将其轉換為IMediaFormat接口來使用。
這就是一個反射的典型應用。
4、如何使用反射擷取類型
首先我們來看如何獲得類型資訊。
獲得類型資訊有兩種方法,一種是得到執行個體對象
這個時侯我僅僅是得到這個執行個體對象,得到的方式也許是一個object的引用,也許是一個接口的引用,但是我并不知道它的确切類型,我需要了解,那麼就可以通過調用System.Object上聲明的方法GetType來擷取執行個體對象的類型對象,比如在某個方法内,我需要判斷傳遞進來的參數是否實作了某個接口,如果實作了,則調用該接口的一個方法:
…
public void Process( object processObj )
{
Type t = processsObj.GetType();
if( t.GetInterface(“ITest”) !=null )
…
}
…
另外一種擷取類型的方法是通過Type.GetType以及Assembly.GetType方法,如:
Type t = Type.GetType(“System.String”);
需要注意的是,前面我們講到了命名空間和裝配件的關系,要查找一個類,必須指定它所在的裝配件,或者在已經獲得的Assembly執行個體上面調用GetType。
本裝配件中類型可以隻寫類型名稱,另一個例外是mscorlib.dll,這個裝配件中聲明的類型也可以省略裝配件名稱(.Net裝配件編譯的時候,預設都引用了mscorlib.dll,除非在編譯的時候明确指定不引用它),比如:
System.String是在mscorlib.dll中聲明的,上面的Type t = Type.GetType(“System.String”)是正确的
System.Data.DataTable是在System.Data.dll中聲明的,那麼:
Type.GetType(“System.Data.DataTable”)就隻能得到空引用。
必須:
Type t = Type.GetType("System.Data.DataTable,System.Data,Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
這樣才可以,大家可以看下面這個文章:
http://expert.csdn.net/Expert/topic/2210/2210762.xml?temp=.1919977
qqchen的回答很精彩
5、如何根據類型來動态建立對象
System.Activator提供了方法來根據類型動态建立對象,比如建立一個DataTable:
Type t = Type.GetType("System.Data.DataTable,System.Data,Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
DataTable table = (DataTable)Activator.CreateInstance(t);
例二:根據有參數的構造器建立對象
namespace TestSpace {
public class TestClass
{
private string _value;
public TestClass(string value) {
_value=value;
}
}
}
…
Type t = Type.GetType(“TestSpace.TestClass”);
Object[] constructParms = new object[] {“hello”}; //構造器參數
TestClass obj = (TestClass)Activator.CreateInstance(t,constructParms);
…
把參數按照順序放入一個Object數組中即可
6、如何擷取方法以及動态調用方法
namespace TestSpace
{
public class TestClass {
private string _value;
public TestClass() {
}
public TestClass(string value) {
_value = value;
}
public string GetValue( string prefix ) {
if( _value==null )
return "NULL";
else
return prefix+" : "+_value;
}
public string Value {
set {
_value=value;
}
get {
if( _value==null )
return "NULL";
else
return _value;
}
}
}
}
上面是一個簡單的類,包含一個有參數的構造器,一個GetValue的方法,一個Value屬性,我們可以通過方法的名稱來得到方法并且調用之,如:
//擷取類型資訊
Type t = Type.GetType("TestSpace.TestClass");
//構造器的參數
object[] constuctParms = new object[]{"timmy"};
//根據類型建立對象
object dObj = Activator.CreateInstance(t,constuctParms);
//擷取方法的資訊
MethodInfo method = t.GetMethod("GetValue");
//調用方法的一些标志位,這裡的含義是Public并且是執行個體方法,這也是預設的值
BindingFlags flag = BindingFlags.Public | BindingFlags.Instance;
//GetValue方法的參數
object[] parameters = new object[]{"Hello"};
//調用方法,用一個object接收傳回值
object returnValue = method.Invoke(dObj,flag,Type.DefaultBinder,parameters,null);
屬性與方法的調用大同小異,大家也可以參考MSDN
7、動态建立委托
委托是C#中實作事件的基礎,有時候不可避免的要動态的建立委托,實際上委托也是一種類型:System.Delegate,所有的委托都是從這個類派生的
System.Delegate提供了一些靜态方法來動态建立一個委托,比如一個委托:
namespace TestSpace {
delegate string TestDelegate(string value);
public class TestClass {
public TestClass() {
}
public void GetValue(string value) {
return value;
}
}
}
使用示例:
TestClass obj = new TestClass();
//擷取類型,實際上這裡也可以直接用typeof來擷取類型
Type t = Type.GetType(“TestSpace.TestClass”);
//建立代理,傳入類型、建立代理的對象以及方法名稱
TestDelegate method = (TestDelegate)Delegate.CreateDelegate(t,obj,”GetValue”);
String returnValue = method(“hello”);
在上一篇文章中所用的方法基本上 MicroSoft Live 和GOOGLE個性首頁 頁面元素可拖拽放置的實作原理,但是你也許會發現一個問題,滑鼠不在可拖動的層之内,而是在與拖動層的相對位置的0,0坐标上,這樣當滑鼠經過未被拖動的層的時候才能觸發這些層的onmouseover事件。如果你為拖動對象設定一個偏移值就可以将滑鼠放置到已拖動層内的位置,但是這樣就出現一個更嚴重的問題,未被拖動的那些層的onmouseover事件觸發不了了,這樣就也無法進行放置動作和放置位置的預覽顯示。
解決方法:當滑鼠在一個層内時是不能觸發另一個層(在這裡是下面的層)的任何滑鼠事件的,是以這時候必須抛棄onmouseover事件,我們需要通過對滑鼠坐标和未被拖動的層的坐标及範圍做判斷,判斷滑鼠是否經過某個層。然後再處理相應的事情,但是這裡又出現一個問題,判斷滑鼠是否在某個層内?但是滑鼠已經在被拖動的層内了,并且在放下之前會一直在這個層内,這樣判斷将會得出兩個符合條件的結果,是以我們要排除被拖動的層,可以有很多解決方法,在這裡我使用一個比較容易了解的方法:不直接判斷滑鼠是否在哪個層,而是判斷被拖動的層是否在某個未被拖動的層内,是以判斷的語句片段是這樣的:obj.style.posTop>rootNode.childNodes[i].offsetTop&&obj.style.posTop<rootNode.childNodes[i].offsetTop+15 這樣的話,第一個條件obj.style.posTop>rootNode.childNodes[i].offsetTop 就将被拖動的本身排除掉了(當rootNode.childNodes[i]是被拖動的這個層obj的時候,style.posTop和offsetTop 是相等的,是以大于>條件就不成立了,被拖動的層自身就這樣被排除的)。
<%@ Page language="c#" Codebehind="DropLayer2.aspx.cs" AutoEventWireup="false" Inherits="test.DropLayer2" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<html>
<head>
<title>DropLayer2</title>
<meta name="GENERATOR" Content="Microsoft Visual Studio .NET 7.1">
<meta name="CODE_LANGUAGE" Content="C#">
<meta name="vs_defaultClientScript" content="JavaScript">
<meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">
<style type="text/css">
div
{
border-right: lightgrey thin solid;
border-top: lightgrey thin solid;
font-weight: bold;
z-index: 2;
text-transform: capitalize;
border-left: lightgrey thin solid;
color: white;
border-bottom: lightgrey thin solid;
background-color: dimgray;
}
</style>
</head>
<body>
<div id="parentDiv" class="parentCss" style="width:100">
<div class="itemCss">one 1</div>
<div class="itemCss">two 2</div>
<div class="itemCss">three 3</div>
<div class="itemCss">four 4</div>
<div class="itemCss">five 5</div>
<div class="itemCss">six 6</div>
<div class="itemCss">seven 7</div>
<div class="itemCss">eight 8</div>
<div class="itemCss">nine 9</div>
<div class="itemCss">ten 10</div>
</div>
<script language="javascript">
<!--
var obj,obj2; //引發事件對象
var rootNode; //控制對象根節點
var IsDrag=false; //是否抓起
var NullDiv; //空臨時層
var x,y; //滑鼠與控件的相對坐标
window.onload = Prepare; //窗體加載時委托到Prepare
function Prepare()
{
//生成臨時層,并設定其屬性
NullDiv = document.createElement("div");
//獲得控制對象的根節點元素
rootNode = document.getElementById("parentDiv");
document.οnmοusemοve=MoveIt; //當滑鼠在文檔上移動時事件委托到MoveIt
document.οnmοusedοwn=Drag; //當滑鼠按下時事件委托到Drag
document.οnmοuseup=Release; //當滑鼠釋放台起時事件委托到Release
}
function Drag()
{
obj = event.srcElement;
x=20;//event.offsetX;
y=10;//event.offsetY;
obj.style.position='absolute';
obj.style.posTop=event.y-y;
obj.style.posLeft=event.x-x;
IsDrag=true;
}
function MoveIt()
{
if(IsDrag)
{
obj.style.posTop=event.y-y;
obj.style.posLeft=event.x-x;
//循環并判斷滑鼠經過哪個未被拖動的層
for(var i = 0 ; i<rootNode.childNodes.length;i++)
{
//判斷滑鼠經過哪個未被拖動的層
if(obj.style.posTop>rootNode.childNodes[i].offsetTop&&obj.style.posTop<rootNode.childNodes[i].offsetTop+15&&obj.style.posLeft>rootNode.childNodes[i].offsetLeft&&obj.style.posLeft<rootNode.childNodes[i].offsetLeft+100)
{
obj2=rootNode.childNodes[i];
NullDiv.style.display='';
NullDiv.style.height='18';
NullDiv.style.width='100';
rootNode.insertBefore(NullDiv,obj2);
break;
}
}
}
}
function Release()
{
IsDrag=false;
NullDiv.style.display='none';
obj.style.position='';
rootNode.insertBefore(obj,obj2);
}
//-->
</script>
</body>
</html>
使用反射動态調用類成員,需要Type類的一個方法:InvokeMember。對該方法的聲明如下(摘抄于MSDN): public object InvokeMember( string name, BindingFlags invokeAttr, Binder binder, object target, object[] args ); 參數 name String,它包含要調用的構造函數、方法、屬性或字段成員的名稱。 - 或 - 空字元串 (""),表示調用預設成員。 invokeAttr 一個位屏蔽,由一個或多個指定搜尋執行方式的 BindingFlags 組成。通路可以是 BindingFlags 之一,如 Public、NonPublic、Private、InvokeMethod 和 GetField 等。不需要指定查找類型。如果省略查找類型,則将應用 BindingFlags.Public | BindingFlags.Instance。 binder 一個 Binder 對象,該對象定義一組屬性并啟用綁定,而綁定可能涉及選擇重載方法、強制參數類型和通過反射調用成員。 - 或 - 若為空引用(Visual Basic 中為 Nothing),則使用 DefaultBinder。 target 要在其上調用指定成員的 Object。 args 包含傳遞給要調用的成員的參數的數組。 傳回值 表示被調用成員的傳回值的 Object。 備注: 下列 BindingFlags 篩選标志可用于定義包含在搜尋中的成員: 為了擷取傳回值,必須指定 BindingFlags.Instance 或 BindingFlags.Static。 指定 BindingFlags.Public 可在搜尋中包含公共成員。 指定 BindingFlags.NonPublic 可在搜尋中包含非公共成員(即私有成員和受保護的成員)。 指定 BindingFlags.FlattenHierarchy 可包含層次結構上的靜态成員。 下列 BindingFlags 修飾符标志可用于更改搜尋的執行方式: BindingFlags.IgnoreCase,表示忽略 name 的大小寫。 BindingFlags.DeclaredOnly,僅搜尋 Type 上聲明的成員,而不搜尋被簡單繼承的成員。 可以使用下列 BindingFlags 調用标志表示要對成員采取的操作: CreateInstance,表示調用構造函數。忽略 name。對其他調用标志無效。 InvokeMethod,表示調用方法,而不調用構造函數或類型初始值設定項。對 SetField 或 SetProperty 無效。 GetField,表示擷取字段值。對 SetField 無效。 SetField,表示設定字段值。對 GetField 無效。 GetProperty,表示擷取屬性。對 SetProperty 無效。 SetProperty 表示設定屬性。對 GetProperty 無效。 下面通過例題對該方法進行簡單應用(我一直以為,要讓例題起到更容易了解文字的意義和作用,撰寫的例題越簡單越直覺越好。) using System; using System.Reflection; namespace ConsoleApplication9 { class Love { public int field1; private string _name; public Love() { } public string Name { get { return _name; } set { _name=value; } } public int GetInt(int a) { return a; } public void Display(string str) { Console.WriteLine(str); } } ///<summary> /// Class1 的摘要說明。 ///</summary> class Class1 { ///<summary> /// 應用程式的主入口點。 ///</summary> [STAThread] static void Main(string[] args) { // // TODO: 在此處添加代碼以啟動應用程式 // Love love = new Love(); Type type = love.GetType(); Object obj = type.InvokeMember(null, BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.CreateInstance, null, null, args); //調用沒有傳回值的方法 type.InvokeMember("Display",BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance , null , obj , new object[]{"aldfjdlf"}); //調用有傳回值的方法 int i = (int)type.InvokeMember("GetInt",BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance,null,obj,new object[]{1}); Console.WriteLine(i); //設定屬性值 type.InvokeMember("Name",BindingFlags.SetProperty,null,obj,new string[]{"abc"}); //擷取屬性值 string str=(string)type.InvokeMember("Name",BindingFlags.GetProperty,null,obj,null); Console.WriteLine(str); //設定字段值 type.InvokeMember("field1",BindingFlags.SetField,null,obj,new object[]{444}); //擷取字段值 int f=(int)type.InvokeMember("field1",BindingFlags.GetField,null,obj,null); Console.WriteLine(f); Console.ReadLine(); } } } 目錄 反射的概述 反射appDomain 的程式集 反射單個程式集 利用反射擷取類型資訊 設定反射類型的成員 通過反射建立類型的執行個體 反射類型的接口 反射的性能 反射的概述 反射的定義:審查中繼資料并收集關于它的類型資訊的能力。中繼資料(編譯以後的最基本資料單元)就是一大堆的表,當編譯程式集或者子產品時,編譯器會建立一個類定義表,一個字段定義表,和一個方法定義表等,。System.reflection命名空間包含的幾個類,允許你反射(解析)這些中繼資料表的代碼 和反射相關的命名空間(我們就是通過這幾個命名空間通路反射資訊): System.Reflection.MemberInfo System.Reflection.EventInfo System.Reflection.FieldInfo System.Reflection.MethodBase System.Reflection.ConstructorInfo System.Reflection.MethodInfo System.Reflection.PropertyInfo System.Type System.Reflection.Assembly 反射的層次模型:
注:層次間都是一對多的關系 反射的作用: 1. 可以使用反射動态地建立類型的執行個體,将類型綁定到現有對象,或從現 有對象中擷取類型 2. 應用程式需要在運作時從某個特定的程式集中載入一個特定的類型,以便實作某個任務時可以用到反射。 3. 反射主要應用與類庫,這些類庫需要知道一個類型的定義,以便提供更多的功能。 應用要點: 1. 現實應用程式中很少有應用程式需要使用反射類型 2. 使用反射動态綁定需要犧牲性能 3. 有些中繼資料資訊是不能通過反射擷取的 4. 某些反射類型是專門為那些clr 開發編譯器的開發使用的,是以你要意識到不是所有的反射類型都是适合每個人的。 反射appDomain 的程式集 當你需要反射AppDomain 中包含的所有程式集,示例如下:
static void Main { //通過GetAssemblies 調用appDomain的所有程式集 foreach (Assembly assem in Appdomain.currentDomain.GetAssemblies()) { //反射目前程式集的資訊 reflector.ReflectOnAssembly(assem) } } 說明:調用AppDomain 對象的GetAssemblies 方法 将傳回一個由System.Reflection.Assembly元素組成的數組。 反射單個程式集 上面的方法講的是反射AppDomain的所有程式集,我們可以顯示的調用其中的一個程式集,system.reflecton.assembly 類型提供了下面三種方法: 1. Load 方法:極力推薦的一種方法,Load 方法帶有一個程式集标志并載入它,Load 将引起CLR把政策應用到程式集上,先後在全局程式集緩沖區,應用程式基目錄和私有路徑下面查找該程式集,如果找不到該程式集系統抛出異常 2. LoadFrom 方法:傳遞一個程式集檔案的路徑名(包括擴充名),CLR會載入您指定的這個程式集,傳遞的這個參數不能包含任何關于版本号的資訊,區域性,和公鑰資訊,如果在指定路徑找不到程式集抛出異常。 3. LoadWithPartialName:永遠不要使用這個方法,因為應用程式不能确定再在載入的程式集的版本。該方法的唯一用途是幫助那些在.Net架構的測試環節使用.net 架構提供的某種行為的客戶,這個方法将最終被抛棄不用。 注意:system.AppDomain 也提供了一種Load 方法,他和Assembly的靜态Load 方法不一樣,AppDomain的load 方法是一種執行個體方法,傳回的是一個對程式集的引用,Assembly的靜态Load 方發将程式集按值封裝發回給發出調用的AppDomain.盡量避免使用AppDomain的load 方法 利用反射擷取類型資訊 前面講完了關于程式集的反射,下面在講一下反射層次模型中的第三個層次,類型反射 一個簡單的利用反射擷取類型資訊的例子: using system; using sytem.reflection; class reflecting { static void Main(string[]args) { reflecting reflect=new reflecting();//定義一個新的自身類 //調用一個reflecting.exe程式集 assembly myAssembly =assembly.loadfrom(“reflecting.exe”) reflect.getreflectioninfo(myAssembly);//擷取反射資訊 } //定義一個擷取反射内容的方法 void getreflectioninfo(assembly myassembly) { type[] typearr=myassemby.Gettypes();//擷取類型 foreach (type type in typearr)//針對每個類型擷取詳細資訊 { //擷取類型的結構資訊 constructorinfo[] myconstructors=type.GetConstructors; //擷取類型的字段資訊 fieldinfo[] myfields=type.GetFiedls() //擷取方法資訊 MethodInfo myMethodInfo=type.GetMethods(); //擷取屬性資訊 propertyInfo[] myproperties=type.GetProperties //擷取事件資訊 EventInfo[] Myevents=type.GetEvents; } } } 其它幾種擷取type對象的方法: 1. System.type 參數為字元串類型,該字元串必須指定類型的完整名稱(包括其命名空間) 2. System.type 提供了兩個執行個體方法:GetNestedType,GetNestedTypes 3. Syetem.Reflection.Assembly 類型提供的執行個體方法是:GetType,GetTypes,GetExporedTypes 4. System.Reflection.Moudle 提供了這些執行個體方法:GetType,GetTypes,FindTypes 設定反射類型的成員 反射類型的成員就是反射層次模型中最下面的一層資料。我們可以通過type對象的GetMembers 方法取得一個類型的成員。如果我們使用的是不帶參數的GetMembers,它隻傳回該類型的公共定義的靜态變量和執行個體成員,我們也可以通過使用帶參數的GetMembers通過參數設定來傳回指定的類型成員。具體參數參考msdn 中system.reflection.bindingflags 枚舉類型的詳細說明。 例如: //設定需要傳回的類型的成員内容 bindingFlags bf=bingdingFlags.DeclaredOnly|bingdingFlags.Nonpublic|BingdingFlags.Public; foreach (MemberInfo mi int t.getmembers(bf)) { writeline(mi.membertype) //輸出指定的類型成員 } 通過反射建立類型的執行個體 通過反射可以擷取程式集的類型,我們就可以根據獲得的程式集類型來建立該類型新的執行個體,這也是前面提到的在運作時建立對象實作晚綁定的功能 我們可以通過下面的幾個方法實作: 1. System.Activator 的CreateInstance方法。該方法傳回新對象的引用。具體使用方法參見msnd 2. System.Activator 的createInstanceFrom 與上一個方法類似,不過需要指定類型及其程式集 3. System.Appdomain 的方法:createInstance,CreateInstanceAndUnwrap,CreateInstranceFrom和CreateInstraceFromAndUnwrap 4. System.type的InvokeMember執行個體方法:這個方法傳回一個與傳入參數相符的構造函數,并構造該類型。 5. System.reflection.constructinfo 的Invoke執行個體方法 反射類型的接口 如果你想要獲得一個類型繼承的所有接口集合,可以調用Type的FindInterfaces GetInterface或者GetInterfaces。所有這些方法隻能傳回該類型直接繼承的接口,他們不會傳回從一個接口繼承下來的接口。要想傳回接口的基礎接口必須再次調用上述方法。 反射的性能: 使用反射來調用類型或者觸發方法,或者通路一個字段或者屬性時clr 需 要做更多的工作:校驗參數,檢查權限等等,是以速度是非常慢的。是以盡量不要使用反射進行程式設計,對于打算編寫一個動态構造類型(晚綁定)的應用程式,可以采取以下的幾種方式進行代替: 1. 通過類的繼承關系。讓該類型從一個編譯時可知的基礎類型派生出來,在運作時生成該類 型的一個執行個體,将對其的引用放到其基礎類型的一個變量中,然後調用該基礎類型的虛方法。 2. 通過接口實作。在運作時,建構該類型的一個執行個體,将對其的引用放到其接口類型的一個變量中,然後調用該接口定義的虛方法。 3 .通過委托實作。讓該類型實作一個方法,其名稱和原型都與一個在編譯時就已知的委托相符。在運作時先構造該類型的執行個體,然後在用該方法的對象及名稱構造出該委托的執行個體,接着通過委托調用你想要的方法。這個方法相對與前面兩個方法所作的工作要多一些,效率更低一些
在調用Office的時候,我們發現Office2003與Office2000的一個方法參數個數不同,這樣導緻我們不能夠使用正常手段調用該方法,采用反射技術可以輕松解決問題。以下是我寫的應用反射技術調用對象方法之簡單原型。
using System;
using System.Reflection;
public class Invoker
{
public static void Main()
{
System.Type myType = System.Type.GetType("Demo");//取得系統類型
object obj = Assembly.GetAssembly(myType).CreateInstance("Demo");//建立執行個體
MethodInfo method = myType.GetMethod("PrintLine");//提取方法資訊
method.Invoke(obj, new object[]{"Rookie", 27});//調用方法
method = myType.GetMethod("PrintLine2");//提取另外一個方法,實際應用中是根據不同版本取得同一個方法,而構造不同參數數組
method.Invoke(obj, new object[]{"Rookie", 27, "Rookie personal information."});//調用方法
}
}
public class Demo
{
public Demo()
{
}
//實際應用中老版本的方法
public void PrintLine(string name, int age)
{
System.Console.WriteLine("Name = " + name + "; Age = " + age.ToString());
}
//實際應用中更新版本的方法(名稱相同,隻是參數個數不同)
public void PrintLine2(string name, int age, string description)
{
System.Console.WriteLine("Name = " + name + "; Age = " + age.ToString() + "; Description = " + description);
}
}