前面老周給大家介紹了動態類型使用的娛樂級別用法,其實,在很多情景下,娛樂級別的用法已經滿足需求了。
如果,你想自己來控制動態類型的行為和數據的存取,那么,就可以考慮用今天所說的高大上技術了。比如,你希望自己弄個字典來存取數據,又或者,你不想用字典,你想用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也是隨意寫的名字。至於說能不能成功調用,就取決於你的自定義動態類型的處理邏輯了。
下面看看裝逼的結果。
好了,高級篇就說完了。改天有空,老周再寫超級篇,會給大伙兒演示一個更牛逼的自定義動態類型。