【.net深呼吸】動態類型(高級篇)


前面老周給大家介紹了動態類型使用的娛樂級別用法,其實,在很多情景下,娛樂級別的用法已經滿足需求了。

如果,你想自己來控制動態類型的行為和數據的存取,那么,就可以考慮用今天所說的高大上技術了。比如,你希望自己弄個字典來存取數據,又或者,你不想用字典,你想用XML來存取數據,那么就必須自己來實現動態對象的行為了。

實現的原理就是從DynamicObject類(位於System.Dynamic命名空間)派生出你自己的類。注意看,這個類的構造函數是protected的,也就是你無法把它實例化,所以,你要從該類派生,來實現你自己的動態行為。

實現方法是重寫該類的虛方法,不一定要全部重寫(你也不可能全部重寫,個別方法不支持VB和C#),最常用的是實現以下三個方法:

TrySetMember:當向對象的動態屬性賦值時用到。

TryGetMember:有set的,自然就有get的,所以當要獲取屬性值時重寫。

TryInvokeMember:當要調用動態的方法時重寫。

 

動態對象的成員是在運行階段來解析的,所以名字是不確定的,這三個方法的第一個參數是一個binder,先不管它是什么類型的binder,因為不同的成員其binder不同。比如,屬性調用是GetMemberBinder,不管是get還是set,都是這個;如果是方法調用則為InvokeMemberBinder。不管是啥binder,它的Name屬性可以返回成員的名字。

例如,obj.Size = 0.55f,那么實現時,binder的Name屬性就返回"Size"。

 

理論太抽象了,給大伙來點干貨吧。下面這個類,我實現了屬性的get和set行為,以及方法調用行為。這個自定義的動態類型,我依然是使用Dictionary<string, object>字典來存取數據。

    public sealed class MyDynamicObject : DynamicObject
    {
        #region 私有字段
        Dictionary<string, object> dic = null;
        #endregion

        #region 構造方法
        public MyDynamicObject()
        {
            dic = new Dictionary<string, object>();
        }
        #endregion

        #region 重寫的方法
        /// <summary>
        /// 設置成員的值,如屬性。
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            // binder.Name屬性表示設置的成員名字,
            // 比如動態對象變量k,k.Age = 22,則binder.Name為"Age",
            // 此處把它轉化為小寫字母,作為字典的Key來存儲
            dic[binder.Name.ToLower()] = value;
            /*
            如果操作成功,返回true;
            如果操作不成功,返回false。
            此處直接操作字典數據,基本不會發生錯誤,
            所以直接返回true。
            */
            return true;
        }

        /// <summary>
        /// 獲取成員的值,如屬性等。
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            string key = binder.Name.ToLower();
            if (dic.ContainsKey(key))
            {
                result = dic[key];
                return true;
            }
            result = null;
            return false;
        }

        /// <summary>
        /// 方法調用實現
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="args"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            result = 0d;
            // 如果參數個數不是1個,則調用失敗
            if (args.Length != 1) return false;
            // 如果參數類型不能隱式轉化為double,則調用失敗
            object a = args[0];
            if (a == null) return false;
            if ((typeof(double).IsAssignableFrom(a.GetType())) == false) return false;
            // 判斷方法名稱
            string methodName = binder.Name.ToLower();
            double num = Convert.ToDouble(a);
            if (methodName == "m2")
            {
                result = Math.Pow(num, 2d); //二次方
            }
            else if (methodName == "m3")
            {
                result = Math.Pow(num, 3d); //三次方
            }
            return true;
        }
        #endregion
    }

 

代碼好像有點長,但是,你別指望我會一行一行地給你解釋,老周的教育理念就是:“非解釋型教學”。這些方法都是返回布爾類型的,你認為調用成功就返回true,你要是認為調用方沒資格調用或調用是錯的,就返回false。至於說返回false會有什么后果,不要問我,你自己試試就知道了,不要向我問那些可以輕松通過實驗驗證的問題。

不過,哦,在實現方法調用的TryInvokeMember方法,我還是要說一下。首先,第一個參數binder就不說了,前面說過了,就是包含方法名字,返回類型等信息。第二個參數args是一個object數組,要是方法沒有參數就是0個元素,要是有2個參數就是2個元素,注意參數是不固定的,都說了,動態對象是運行時才解析的,一切都是不確定的。最后的result參數是方法的返回值,out方向,所以在TryInvokeMember返回前你必須給它賦值。

在TryInvokeMember實現代碼中,有這個表達式,可能大伙不是很熟,就這個:

typeof(double).IsAssignableFrom(a.GetType())

它的意思是參數中的類型的變量能不能賦值給當前類型的變量,比如本例,當前類型是double,用IsAssignableFrom來驗證一下指定的type的變量能不能賦值給double類型的變量

如果a的類型為int,它是可以賦值給double類型的變量的。

 

好了,自定義的動態類型寫好了,下面就用它來裝裝逼吧。

            dynamic dobj = new MyDynamicObject();
            // 成員賦值
            dobj.Title = "好消息";
            dobj.Content = "京東全場0.5折優惠。";
            // 輸出
            Console.WriteLine($"標題:{dobj.Title}\n內容:{dobj.Content}");

            // 調用方法
            Console.WriteLine("10的平方  {0}", dobj.M2(10d));
            Console.WriteLine("3的立方  {0}", dobj.M3(3d));

 

Title和Content屬性都是隨便命名的,因為編譯階段不檢查,所以可以隨便寫,但是,你取值時用的名字必須要和賦值時的名字相同。同理,方法M1和M2也是隨意寫的名字。至於說能不能成功調用,就取決於你的自定義動態類型的處理邏輯了。

 

下面看看裝逼的結果。

 

好了,高級篇就說完了。改天有空,老周再寫超級篇,會給大伙兒演示一個更牛逼的自定義動態類型。

 

示例源代碼下載

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM