C#是一門靜態類型的語言,但是在C#4.0時微軟引入了動態類型的概念。
dynamic
關鍵字dynamic用來定義動態對象,我們來看一下動態類型的一些特性。
調用不同類的相同方法
我們有兩個或多個不相關的類,然后運行時需要可以調用到相同名稱的方法,如下:
1 using System; 2 3 namespace Study 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 dynamic obj = GetObject(0); 10 Console.WriteLine(obj.Talk()); 11 12 Console.Read(); 13 } 14 15 private static Object GetObject(int type) 16 { 17 switch (type) 18 { 19 case 1: 20 return new Dog(); 21 } 22 return new Robot(); 23 } 24 } 25 26 public class Dog 27 { 28 public string Talk() 29 { 30 return "Wang Wang!"; 31 } 32 } 33 34 public class Robot 35 { 36 public string Talk() 37 { 38 return "I`m a Robot!"; 39 } 40 } 41 }
我們的兩個類沒有繼承也沒有應用相同的接口,但是可以調用到相同的方法,使用GetObject(1)可以得到想要的結果。
這就是動態類型,在編譯時不會對方法等進行判斷,而是在運行時才進行處理,如果調用到不存在的方法才會報錯。
C#編譯器允許你通過dynamic對象調用任何方法,即使這個方法根本不存在,編譯器也不會在編譯的時候報編譯錯誤。只有在運行的時候,它才會檢查這個對象的實際類型,並檢查在它上面Talk()是什么意思。動態類型將使得C#可以以更加統一而便利的形式表示下列對象:
- 來自動態編程語言——如Python或Ruby——的對象;
- 通過IDispatch訪問的COM對象;
- 通過反射訪問的一般.NET類型;
- 結構發生過變化的對象——如HTML DOM對象;
當我們得到一個動態類型的對象時,不管它是來自COM還是IronPython、HTML DOM還是反射,只需要對其進行操作即可,動態語言運行時(DLR)會幫我們指出針對特定的對象以及這些操作的具體意義。這將給我們的開發帶來極大的靈活性,並且能夠極大程度上地精簡我們的代碼。
動態類型使用注意
- 不能調用擴展方法;
- 委托與動態類型不能進行隱式轉換;
- 不能調用構造函數和靜態方法;
- 類不能繼承dynamic、泛型參數不能使用dynamic和接口實現也不能使用dynamic;
實現動態行為
實現動態行為有3種方法,分別可以用在不同的場合。
使用ExpandoObject類
直接使用ExpandoObject類來實現動態行為,代碼如下:
1 using System; 2 using System.Dynamic; 3 4 namespace Study 5 { 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 dynamic obj = new ExpandoObject(); 11 //添加屬性 12 obj.name = "Li Lei"; 13 obj.age = 20; 14 //添加方法 15 obj.Add = (Func<int, int, int>) ((a, b) => a + b); 16 17 Console.WriteLine("Name: " + obj.name); 18 Console.WriteLine("Age: " + obj.age); 19 Console.WriteLine("Add: " + obj.Add(100, 123)); 20 21 Console.Read(); 22 } 23 } 24 }
輸出如下:
1 Name: Li Lei 2 Age: 20 3 Add: 223
繼承DynamicObject類
通過繼承DynamicObject類也可以實現動態效果,示例如下:
1 using System; 2 using System.Dynamic; 3 4 namespace Study 5 { 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 dynamic obj = new MyClass(); 11 obj.name = "Li Lei"; 12 obj.age = 20; 13 obj.CallFunc(); 14 15 Console.Read(); 16 } 17 } 18 19 public class MyClass : DynamicObject 20 { 21 public override bool TrySetMember(SetMemberBinder binder, object value) 22 { 23 Console.WriteLine("設置" + binder.Name + "為" + value); 24 return true; 25 } 26 27 public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) 28 { 29 Console.WriteLine("調用" + binder.Name + "方法"); 30 result = null; 31 return true; 32 } 33 } 34 }
輸出如下:
1 設置name為Li Lei 2 設置age為20 3 調用CallFunc方法
實現IDynamicMetaObjectProvider接口
如果已經繼承了其它的類,則可以通過實現IDynamicMetaObjectProvider接口來實現動態行為,例子如下:
1 using System; 2 using System.Dynamic; 3 using System.Linq.Expressions; 4 5 namespace Study 6 { 7 class Program 8 { 9 static void Main(string[] args) 10 { 11 dynamic obj = new MyClass(); 12 obj.CallFunc(); 13 14 Console.Read(); 15 } 16 } 17 18 public class MyClass : IDynamicMetaObjectProvider 19 { 20 public DynamicMetaObject GetMetaObject(Expression parameter) 21 { 22 Console.WriteLine("獲取元數據"); 23 return new MetaDynamic(parameter, this); 24 } 25 } 26 27 public class MetaDynamic : DynamicMetaObject 28 { 29 public MetaDynamic(Expression expression, object value) : base(expression, BindingRestrictions.Empty, value) 30 { 31 } 32 33 public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) 34 { 35 MyClass target = base.Value as MyClass; 36 Expression self = Expression.Convert(base.Expression, typeof (MyClass)); 37 var restrictions = BindingRestrictions.GetInstanceRestriction(self, target); 38 Console.WriteLine("調用" + binder.Name + "方法"); 39 return new DynamicMetaObject(self, restrictions); 40 } 41 } 42 }
輸出如下:
1 獲取元數據 2 調用CallFunc方法