C# 反射、與dynamic最佳組合

在 C# 中反射技術應用普遍,至於什麼是反射.........你若是不瞭解的話,請看下段說明,不然請跳過下段。廣告一下:喜歡我文章的朋友請關注一下個人blog,這也有助於提升本人寫做的動力。html

反射:當你背對一個美女或帥哥卻不能回頭仔細觀察研究時(純屬虛構,若有巧合、純屬雷同),一面小鏡子就能知足你的需求。在 C# 編程過程當中也常常遇到相似的狀況:有一個別人寫的 dll 類庫你想使用卻沒程序文檔資料......此時經過 C# Runtime 提供的功能,你能夠把該 dll 類庫加載到你的程序中,並細細研究 dll 的每一部份內容,這就是 C# 中的反射。web

我的認爲反射最突出的優勢或存在的合理性:在不修改程序原碼的狀況下,實現程序功能的動態調整(Runtime動態對象建立數據庫

示例:編程

    interface IRun {
        void Run();
    }
    class Person : IRun
    {
        public void Run()
        {
            Console.WriteLine("走,去LOL啊!");
        }
    }
    class Car : IRun
    {
        public void Run()
        {
            Console.WriteLine("嗚...........");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            IRun e = new Person();
            e.Run();
            Console.ReadLine();
        }
    }

若是將上面的Run功能並不必定是由Person來執行,有時須要是Car有時須要Person。常見的解決方案是添加 if 等判斷結構,以下:設計模式

       static void Main(string[] args)
        {
            Console.WriteLine("請輸入:Car或Person");
            string type = Console.ReadLine();
            IRun e = null;
            if ("Car" == type)
            {
                e = new Car();
            }else if("Person" == type)
            {
                e = new Person();
            }
            if(null != e)
                e.Run();

            Console.ReadLine();
        }

這種結構確是解決了如今的需求,但並不健壯。隨着 IRun 接口實現、相關類的繼承的增長,上面的判斷結構也會飛速增加。面向對象編程、設計模式均遵循的一大原則就是封裝變換,因此上面的程序沒法很好的應對變化。在此咱們並不涉及 「設計模式的」 的知識,所以下面的示例代碼只爲簡化上面的程序、並未刻意套用設計模式相關知識。以下:緩存

        static void Main(string[] args)
        {
            Console.WriteLine("請輸入:Car或Person");
            string type = Console.ReadLine();
            string classPath = String.Format("namespace.{0}", type);
            IRun e = Activator.CreateInstance(null, classPath).Unwrap() as IRun;

            if(null != e)
                e.Run();

            Console.ReadLine();
        }

通過上面的修改,程序可自行根據用戶的輸入,經過Activator.CreateInstance建立 IRun 的實例,程序此處不會再隨 IRun 的實現者增多這種問題的影響而發生變化。上面的這種優勢就是經過反射獲得的,也是我所認爲的「反射存在的合理性」。oracle

Activator、Assembly 實現反射方式建立對象負載均衡

C#中反射方式建立對象能夠經過 Activator.CreateInstance(靜態)和 Assembly.CreateInstance(非靜態)來實現,其中Assembly.CreateInstance 內部調用的還是Activator.CreateInstance。異步

根據要動態建立的類型對象是否處於當前程序集之中,可將反射建立對象分爲:建立程序集內的類型對象與建立程序集外的類型對象。異步編程

建立程序集內的類型對象

        private static void ReflectionIRun1(string className)
        {
            string classPath = String.Format("namespace.{0}", className);
            //參數 null ,指出所要建立類型對象位於當前程序集 
            var handler = Activator.CreateInstance(null, classPath);
            IRun e = (IRun)handler.Unwrap();
            Console.WriteLine(e.Run());
        }
        private static void ReflectionIRun2(string className)
        {
            string classPath = String.Format("namespace.{0}", className);
            //typeof(IRun).Assembly 獲取 IRun 類型所在的程序集
            object obj = typeof(IRun).Assembly.CreateInstance(null, classPath);
            IRun e = (IRun)obj;
            Console.WriteLine(e.Run());
        }

建立程序集外的類型對象

項目中增長一個 類庫 (另外一個程序集),以下圖:

添加一個 Boss 類,以下:

namespace Lib
{
    public class Boss
    {
        private string name = "老大";
        
        public string Name{
            get {return name;}
        }
        public string Talk()
        {
            return "大家都被開除了......";
        }
        //老闆不會算帳,老是多付錢,因此頗有自知之明的將Payfor設爲private,防止外部人員調用
        private int Payfor(int total)
        {
            return total + 10;
        }
    }
}    

獲取 一個 Boss 對象前,首先添加對 Lib 的引用,獲取示例以下:

        private static void ReflectionBoss1()
        {
            string classPath ="Lib.Boss";
            //"Lib" 參數指明要加載的程序集(即要建立的對象類型在哪一個程序集中定義)
            var handler = Activator.CreateInstance("Lib", classPath);
            Boss b = handler.Unwrap() as Boss;
            Console.WriteLine(b.Talk());
        }
        private static void ReflectionBoss2()
        {
            string classPath ="Lib.Boss";
            //Assembly.Load("Lib") 加載的程序集(即要建立的對象類型在哪一個程序集中定義)
            var assembly = Assembly.Load("Lib");
            Boss b = (Boss)assembly.CreateInstance(classPath);
            Console.WriteLine(b.Talk());
        }  

關於反射時CLR如何查找並定位要加載的程序集,請參考MSDN中關於反射相關的知識。

反射訪問字段、調用方法(屬性

反射除能夠幫咱們動態建立對象外,還可幫咱們動態訪問對象的方法(屬性)或字段,因 C# 版本不一樣具體方法會有變動或擴展,更深刻內容請參考MSDN。下面僅做簡單示例(標準用法)。

給老闆更名,示例: 

        private static void ReflectionBoss1()
        {
            string classPath = "Lib.Boss";
            //"Lib" 參數指明要加載的程序集(即要建立的對象類型在哪一個程序集中定義)
            var handler = Activator.CreateInstance("Lib", classPath);
            Boss b = handler.Unwrap() as Boss;
            //關鍵代碼
            FieldInfo f = b.GetType().GetField("name", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
            f.SetValue(b, "小二");

            Console.WriteLine("{0}:{1}", b.Name, b.Talk());
        }

輸出:

讓老闆付錢:

private static void ReflectionBoss1()
        {
            string classPath = "Lib.Boss";
            //"Lib" 參數指明要加載的程序集(即要建立的對象類型在哪一個程序集中定義)
            var handler = Activator.CreateInstance("Lib", classPath);
            Boss b = handler.Unwrap() as Boss;
            //關鍵代碼
            MethodInfo method = b.GetType().GetMethod("Payfor", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance);
            object money = method.Invoke(b, new object[] { 10 });
Console.WriteLine(
"DW039:老大給我報銷10元錢車費......"); Console.WriteLine("{0}:.....,算不清了,給你這些吧。",b.Name); Console.WriteLine("DW039:......"); Console.WriteLine("{0}:{1}", b.Name,money); Console.WriteLine("DW039:老大你真棒!"); }

輸出:

dynamic 與 反射 雙劍合璧

由於反射是運行時的類型操做,因此在編程時面臨類型不肯定的問題。根據上一篇《C# 匿名對象(匿名類型)、var、動態類型 dynamic》講得 dynamic 動態類型結合咱們編寫的反射程序,能夠大大優化程序邏輯(訪問受保護級別限制的代碼不在此範圍內)。

上面代碼的優化:

 private static void ReflectionBoss1()
        {
            string classPath ="Lib.Boss";
            var handler = Activator.CreateInstance("Lib", classPath);
            dynamic b = handler.Unwrap();
            Console.WriteLine(b.Talk());
        }
        private static void ReflectionBoss2()
        {
            string classPath ="Lib.Boss";
            var assembly = Assembly.Load("Lib");
            dynamic b = assembly.CreateInstance(classPath);
            Console.WriteLine(b.Talk());
        }  

經過 dynamic 動態類型對象 b 來調用反射獲得對象的屬性、方法可直接調用,從而省去了頻繁的類型轉換操做。

反射常見應用場景

應用場景我印象最深入的是 MS Petshop 示例,從SQL Server 數據庫切換到 oracle 數據庫時反射得到不一樣的數據訪問層。然我實際項目中從未遇到過中途切換數據庫的狀況,其餘應用場景基本相似上面的示例。若是朋友你發現更多的應用場景,請給予補充,3ks。

反射的優缺點

優勢:反射使程序更靈活

缺點:反射運行速度相對較慢

至於反射相比普通程序慢,我沒有進行過測試也不打算進行。現實狀況是:Ms提倡使用 dynamic、Mvc流行、Ms對CLR不斷優化、機器性能的提高,因此你在開發中無需過多考慮反射的性能問題。若是你寫的程序運行速度出現了瓶頸(應首先確保本身程序寫的合理),研究一下數據庫優化、數據緩存、web緩存、負載均衡等技術我認爲更實際一些。

請放心大膽的使用反射技術吧,朋友!

隨後我將寫一系列關於C# 異步編程的文章,感興趣的朋友請點下面的 「關注我」 ,謝謝。

相關文章
相關標籤/搜索