反射是.net中的進階功能之一,利用反射可以實作許多以前看來匪夷所思的功能,下面是我看了《Programming C#》(O'Reilly)之後對于反射的一點實踐,本想直接做個應用程式來說明問題,但苦于工作繁忙并考慮到以簡單為主,故先對反射發送(reflection emit)的使用做一些介紹。文章最後再給出一個執行個體。
下面的程式在運作時生成了一個Test.cs檔案,并調用csc編譯成Test.dll檔案,然後利用Type.InvokeMember()方法調用其中的SayHello()方法,然後和原始方法對比一下性能。
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
namespace InvokeMember
{
///
/// Class1 的摘要說明。
///
class Class1 { ///
/// 應用程式的主入口點。
///
[STAThread]
static void Main( string [] args)
{
// 循環次數
const int iterations = 100 ;
// 計算所用時間
DateTime startTime = DateTime.Now;
for ( int i = 0 ;i < iterations;i ++ ) { // 對照方法
Console.WriteLine( " Hello,World " );
}
TimeSpan elasped = DateTime.Now - startTime;
Console.WriteLine( " Looping Elapsed milliseconds: " + elasped.TotalMilliseconds + " for {0} iterations " ,iterations);
// 使用反射發送
ReflectionTest t = new ReflectionTest();
// 計算所用時間
startTime = DateTime.Now;
for ( int i = 0 ;i < iterations;i ++ )
{
t.DoOperation();
}
elasped = DateTime.Now - startTime;
Console.WriteLine( " Looping Elapsed milliseconds: " + elasped.TotalMilliseconds + " for {0} iterations " ,iterations);
Console.ReadLine();
}
}
///
/// Reflection 的摘要說明。
///
public class ReflectionTest
{
// 儲存動态生成并編譯的類的type對象
Type theType = null ;
// 儲存動态生成類的執行個體
object theClass = null ;
///
/// 供Client調用的方法
///
public void DoOperation()
{
// 未初始化
if (theType == null )
{
// 初始化
GenerateCode();
}
// 調用方法時的參數數組(此處為空)
object [] arguments = new object [ 0 ];
// 調用動态生成類的方法
theType.InvokeMember( " SayHello " , // 要調用的方法名
BindingFlags.Default | BindingFlags.InvokeMethod, // Binding标志,具體參看msdn
null , // 使用預設Binding對象
theClass, // 在theClass執行個體上調用此方法
arguments // 調用方法時的參數數組
);
}
///
/// 運作時生成代碼
///
private void GenerateCode()
{
// 檔案名
string fileName = " Test " ;
// 打開檔案,如果不存在,則建立
Stream s = File.Open(fileName + " .cs " ,FileMode.Create);
// 建立一個StreamWriter來寫入資料
StreamWriter wrtr = new StreamWriter(s);
// 寫入動态建立類的源代碼
wrtr.WriteLine( " // 動态建立Test類 " );
// 類名
string className = " TestClass " ;
wrtr.WriteLine( " using System; " );
wrtr.WriteLine( " class {0} " ,className);
wrtr.WriteLine( " { " );
wrtr.WriteLine( " /tpublic void SayHello() " );
wrtr.WriteLine( " /t{ " );
wrtr.WriteLine( " /t/tConsole.WriteLine(/"Hello,World/"); " );
wrtr.WriteLine( " /t} " );
wrtr.WriteLine( " } " );
// 關閉StreamWriter和檔案
wrtr.Close();
s.Close();
// 啟動程序編譯源檔案
// 指定參數
ProcessStartInfo psi = new ProcessStartInfo();
// 啟動cmd.exe
psi.FileName = " cmd.exe " ;
// cmd.exe的參數,/c-close,完成後關閉;後為參數,指定cmd.exe使用csc來編譯剛才生成的源檔案
string compileString = " /c C://WINNT//Microsoft.NET//Framework//v1.1.4322//csc.exe /optimize+ /target:library {0}.cs " ;
psi.Arguments = String.Format(compileString,fileName);
// 運作時的風格-最小化
psi.WindowStyle = ProcessWindowStyle.Minimized;
// 啟動程序
Process proc = Process.Start(psi);
// 指定目前在此程序退出前等待
proc.WaitForExit();
// 從編譯好的dll檔案load一個Assembly
Assembly a = Assembly.LoadFrom(fileName + " .dll " );
// 建立類的執行個體
theClass = a.CreateInstance(className);
// 取得此類執行個體的類型
theType = a.GetType(className);
// 删除源檔案
// File.Delete(flieName + ".cs");
}
}
}
程式運作結果:
Hello,World
Hello,World
.
.
.
Looping Elapsed milliseconds:93.75 for 100 iterations
Hello,World
Hello,World
.
.
.
Looping Elapsed milliseconds:2875 for 100 iterations
性能上不占優勢,主要是因為要進行寫檔案和編譯的工作,但是後面的方法會對性能進行優化,到最後一個方法時性能會有大幅提高,但是最後一種方法的實用性不如前兩種。