C#反射(System.Reflection )

TolyHuang發表於2009-03-06

[@more@]
C# System.Reflection 反射

反射技術
我經常在部落格園看到運用反射技術的***,可是遺憾自己一直不知道什麼是
,急忙找些資料學習學習。
程式集包含模組,而模組包含型別,型別又包含成員。反射則提供了封裝程式集、模組和型別的物件。您可以使用反射動態地建立型別的例項,將型別繫結到現有物件,或從現有物件中獲取型別。然後,可以呼叫型別的方法或訪問其欄位和屬性。反射通常具有以下用途:
使用 Assembly 定義和載入程式集,載入在程式集清單中列出的模組,以及從此程式集中查詢型別並建立該型別的例項。
使用 Module 瞭解如下的類似資訊:包含模組的程式集以及模組中的類等。您還可以獲取在模組上定義的所有全域性方法或其他特定的非全域性方法。
使 用 ConstructorInfo 瞭解如下的類似資訊:建構函式的名稱、引數、訪問修飾符(如 public 或 private)和實現詳細資訊(如 abstract 或 virtual)等。使用 Type 的 GetConstructors 或 GetConstructor 方法來呼叫特定的建構函式。
使用 MethodInfo 來了解如下的類似資訊:方法的名稱、返回型別、引數、訪問修飾符(如 public 或 private)和實現詳細資訊(如 abstract 或 virtual)等。使用 Type 的 GetMethods 或 GetMethod 方法來呼叫特定的方法。
使用 FieldInfo 來了解如下的類似資訊:欄位的名稱、訪問修飾符(如 public 或 private)和實現詳細資訊(如 static)等;並獲取或設定欄位值。
使用 EventInfo 來了解如下的類似資訊:事件的名稱、事件處理程式資料型別、自定義屬性、宣告型別和反射型別等;並新增或移除事件處理程式。
使用 PropertyInfo 來了解如下的類似資訊:屬性的名稱、資料型別、宣告型別、反射型別和只讀或可寫狀態等;並獲取或設定屬性值。
使用 ParameterInfo 來了解如下的類似資訊:引數的名稱、資料型別、引數是輸入引數還是輸出引數,以及引數在方法簽名中的位置等。


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");
這樣才可以,大家可以看下面這個帖子:

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”);

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/9437124/viewspace-1018181/,如需轉載,請註明出處,否則將追究法律責任。

相關文章