問題回顧在上篇博客中,,我介紹了優(yōu)化反射的第一個步驟:用委托調(diào)用代替直接反射調(diào)用,。 如果我們將委托保存在字典集合中,會發(fā)現(xiàn)這種設(shè)計會浪費較多的執(zhí)行時間,,因為這種設(shè)計會引發(fā)三個新問題: 再來回顧一下上次的測試結(jié)果吧: 雖然通用接口ISetValue將反射性能優(yōu)化了37倍,但是最終的FastSetValue將這個數(shù)字減少到還不到7倍(在CLR4中還不到5倍),。 再看看直接調(diào)用與反射調(diào)用的對比,它們的速度相差了上千倍,! 能不能不使用委托,?既然委托最后引出了三個難以解決的問題,導(dǎo)致優(yōu)化后速度比直接調(diào)用差距太遠,,那我們能不能不使用委托呢,? 委托調(diào)用并不是優(yōu)化反射的唯一方案,我們還有其它方法,, 假如我需要用客戶端提交的數(shù)據(jù)來填充某個數(shù)據(jù)對象,考慮到代碼的通用性,,我會用反射寫成這樣: /// <summary> /// 從HttpRequest加載obj所需的數(shù)據(jù) /// </summary> /// <param name="request"></param> /// <param name="obj"></param> public static void LoadDataFromHttpRequest(HttpRequest request, object obj) { PropertyInfo[] properties = obj.GetType().GetProperties(); foreach( PropertyInfo p in properties ) { // 這里只是示意代碼,,假設(shè)數(shù)據(jù)處理不會有異常。 object val = Convert.ChangeType(request[p.Name], p.PropertyType); p.FastSetValue(obj, val); } } 如果我事先知道要加載已知的數(shù)據(jù)類型,,代碼會寫成這樣: public static void LoadDataFromHttpRequest(HttpRequest request, OrderInfo order) { // 這里只是示意代碼,,假設(shè)數(shù)據(jù)處理不會有異常。 order.OrderID = int.Parse(request["OrderID"]); order.OrderDate = DateTime.Parse(request["OrderDate"]); order.SumMoney = decimal.Parse(request["SumMoney"]); order.Comment = request["Comment"]; order.Finished = bool.Parse(request["Finished"]); } 顯然,,第二段代碼運行效率更快(盡管第一段代碼調(diào)用FastSetValue優(yōu)化了速度),。 大家都知道反射性能較差,直接調(diào)用性能最好,,那么能不能在運行時不使用反射呢,? 的確,使用反射是因為我們事先不知道要處理哪些類型的對象,,因此不得不用反射,, 另外,反射的代碼也更通用,,寫一個方法可以加載所有的數(shù)據(jù)類型,,可認為是一勞永逸的方法,。 不過,就算我們事先不知道要處理哪些對象類型,,但是只要使用反射,,我們完全可以知道任何一個類型包含哪些數(shù)據(jù)成員,, 還能知道這些數(shù)據(jù)成員的數(shù)據(jù)類型,,這一點不用懷疑吧? 既然我們用反射可以知道所有的類型定義信息,,我們是否可以參照代碼生成器的思路去生成代碼呢,? 我們可以參照前面第二段代碼,為【需要處理的類型】生成直接調(diào)用的代碼,,這樣不就徹底解決了反射性能問題了嗎,? 生成代碼的過程,其實也就是個字符串的拼接過程,,難度并不大,,只是比較復(fù)雜而已。 如果前面的答案都是肯定的,,那么現(xiàn)在只有一個問題了:我們能在運行時執(zhí)行拼接生成的字符串代碼嗎,? 答案也是肯定的:能! CodeDOM:在運行時編譯代碼回憶一下我們編寫的ASPX頁面,,它們并不是C#代碼,,它們本質(zhì)上就是一個文本文件, 我們可以寫入一些HTML標簽,,還有些標簽上加了 runat="server" 屬性,, 我們還可以在頁面中插入一些C#代碼片段,盡管它們不是我們編譯后的DLL文件,,然而它們就是運行起來了,! 要知道ASP.NET不是ASP,ASP是解釋性的腳本語言,,而ASP.NET是以編譯方式運行的,, 所以,每個ASPX頁面文件最后都是運行編譯后的結(jié)果,。 假設(shè)我有下面一段文本(文本的內(nèi)容是一段C#代碼): using System; using System.Collections.Generic; using System.Text; using System.Reflection; namespace OptimizeReflection { public class DemoClass { public int Id { get; set; } public string Name; public int Add(int a, int b) { return a + b; } } public class 用戶手冊 { public static void Main() { // OptimizeReflection 這個類庫提供了一些擴展方法,,它們用于優(yōu)化常見的反射場景 // 下面是一些相關(guān)的演示示例。 // 對于屬性的讀寫操作,、方法的調(diào)用操作,,還提供了性能更好的強類型(泛型)版本,可參考Program.cs Type instanceType = typeof(DemoClass); PropertyInfo propertyInfo = instanceType.GetProperty("Id"); FieldInfo fieldInfo = instanceType.GetField("Name"); MethodInfo methodInfo = instanceType.GetMethod("Add"); // 1. 創(chuàng)建實例對象 DemoClass obj = (DemoClass)instanceType.FastNew(); // 2. 寫屬性 propertyInfo.FastSetValue(obj, 123); propertyInfo.FastSetValue2(obj, 123); // 3. 讀屬性 int a = (int)propertyInfo.FastGetValue(obj); int b = (int)propertyInfo.FastGetValue2(obj); // 4. 寫字段 fieldInfo.FastSetField(obj, "Fish Li"); // 5. 讀字段 string s = (string)fieldInfo.FastGetValue(obj); // 6. 調(diào)用方法 int c = (int)methodInfo.FastInvoke(obj, 1, 2); int d = (int)methodInfo.FastInvoke2(obj, 3, 4); Console.WriteLine("a={0}; b={1}; c={2}; d={3}; s={4}", a, b, c, d, s); } } } 您可以把上面這段文本想像成前面第二個版本的LoadDataFromHttpRequest方法,,如果我們在運行時使用反射也能生成那樣的代碼,, 現(xiàn)在就差把它編譯成程序集了,。下面的代碼演示了如何將一段文本編譯成程序集的過程: string code = null; // 1. 生成要編譯的代碼。(示例為了簡單直接從程序集內(nèi)的資源中讀?。? Stream stram = typeof(CodeDOM).Assembly .GetManifestResourceStream("TestOptimizeReflection.用戶手冊.txt"); using( StreamReader sr = new StreamReader(stram) ) { code = sr.ReadToEnd(); } //Console.WriteLine(code); // 2. 設(shè)置編譯參數(shù),,主要是指定將要引用哪些程序集 CompilerParameters cp = new CompilerParameters(); cp.GenerateExecutable = false; cp.GenerateInMemory = true; cp.ReferencedAssemblies.Add("System.dll"); cp.ReferencedAssemblies.Add("OptimizeReflection.dll"); // 3. 獲取編譯器并編譯代碼 // 由于我的代碼使用了【自動屬性】特性,所以需要 C# .3.5版本的編譯器,。 // 獲取與CLR匹配版本的C#編譯器可以這樣寫:CodeDomProvider.CreateProvider("CSharp") Dictionary<string, string> dict = new Dictionary<string, string>(); dict["CompilerVersion"] = "v3.5"; dict["WarnAsError"] = "false"; CSharpCodeProvider csProvider = new CSharpCodeProvider(dict); CompilerResults cr = csProvider.CompileAssemblyFromSource(cp, code); // 4. 檢查有沒有編譯錯誤 if( cr.Errors != null && cr.Errors.HasErrors ) { foreach( CompilerError error in cr.Errors ) Console.WriteLine(error.ErrorText); return; } // 5. 獲取編譯結(jié)果,,它是編譯后的程序集 Assembly asm = cr.CompiledAssembly; 整個過程分為5個步驟,它們已用注釋標識出來了,,這里不再重復(fù)了,。 如何調(diào)用編譯結(jié)果前面的代碼把一段文本字符串編譯成了程序集,現(xiàn)在還有最后一個問題:如何調(diào)用編譯結(jié)果,? 答案:有二種方法,,
第一種方法要求在生成代碼時,,生成的類名和方法名是明確的,在調(diào)用方法時,,我們有二個選擇: 第二種方法要求在生成代碼時,,首先要定義一個接口,保證生成的代碼能實現(xiàn)指定的接口,, 這二種方法也可以這樣區(qū)分: 對于前面的示例,,我采用了第一種方法了,,因為類名和方法名稱都是事先確定的而且實現(xiàn)起來比較簡單。 // 6. 找到目標方法,并調(diào)用 Type t = asm.GetType("OptimizeReflection.用戶手冊"); MethodInfo method = t.GetMethod("Main"); method.Invoke(null, null); 能不能不使用委托,? 如何用好CodeDOM,? 招聘信息
公司需要若干名 .net 方面的高級開發(fā)人員,要求熟悉以下技術(shù)領(lǐng)域: 說明: |
|