在C#4.0中,最核心的特性莫過於動態類型的引入。
1、動態類型簡介
一直在強調C#是一門靜態類型的語言,因為它在定義變量時要明確給出變量的類型。例如在int i=5;這樣的代碼中,int就是變量i的類型,如果定義變量時沒有明確指定變量的類型,則這樣的代碼是通過不了編譯的。
在C#4.0中,微軟引入了dynamic關鍵字來定義動態類型。當我們使用由dynamic關鍵字限制的變量時,編譯器並不知道它的類型,該類型只能在程序運行的時候才能被確定。動態類型的定義如下面的代碼所示:
dynamic i=5;
從上面這行代碼可以看出,定義動態類型的過程非常簡單,只需要把之前的int類型修改為dynamic關鍵字即可。
那靜態類型和動態類型到底有什麼不同呢?以代碼來說明:
object obj=10;
obj=obj+10;//編譯錯誤
dynamic i=10;//動態類型定義
i=i+10;
在以上的代碼中,第一行obj為object類型,而編譯器卻檢測出“+”運算符無法應用於object和int類型。要讓代碼通過編譯,必須使用強制類型轉換。動態類型定義就不需要轉換了,動態類型定義的變量的具體類型只能在程序運行時被確定,編譯器根本不知道其類型是什麼,所以也就不會出現編譯錯誤了。
2、C#為什麼要引入動態類型
2.1 可以減少強制類型轉換的使用
2.2 調用Python等動態語言
動態類型使得C#語言調用Python這樣的動態語言成為了可能。比如:
1 ScriptEngine engine=Python.CreateEngine();
2 Console.WriteLine("調用Python語言的print函數輸出:");
3 engine.Execute("print 'Hello world'");
4 Console.Read();
以上代碼實現了在C#中調用Python的print函數完成消息打印的過程,但要使這段代碼能夠運行,還必須下載IronPython。IronPython是在.NET Framework上實現的一種動態語言。
由於動態類型變量的具體類型只能在運行時確定,所以智能提示對於動態類型變量並不管用。開發人員在使用動態類型時,必須准確地知道類型所具有的成員,否則在程序運行時很難發現相關錯誤。
3、動態類型約束
動態類型的使用需要滿足一下幾個約束條件:
3.1 不能用來調用擴展方法
不能用動態類型作為參數來調用擴展方法,例如下面的代碼將導致編譯錯誤:
var numbers=Enumerable.Range(10,10);
dynamic number=4;
var error=numbers.Take(number);//編譯錯誤
出現這種錯誤的原因在於:擴展方法是在另一個文件中被定義的,若參數為動態類型,編譯器將無法確定參數的具體類型,因為也就不知道該導入哪個源文件了。我們可以通過兩個方法來解決這個問題:
第一種是將動態類型強制轉換為正確的類型:var right1=numbers.Take((int)number);
第二種是使用靜態方法來調用擴展方法:var right2=Enumerable.Take(numbers,number);
這兩種方法雖然都可能,但還是不推薦在調用擴展方法時使用動態類型。
3.2 委托與動態類型間不能做隱式轉換
不能將Lambda表達式定義為動態類型,因為他們之間不存在隱式轉換,如下面的代碼就會出現編譯錯誤:
dynamic lambdarestrict=x=>x+1;//編譯時錯誤
如果硬要把Lambda表達式定義為動態類型,就需要明確指定委托的類型,具體的解決方案如下:
dynamic rightLambda=(Func<int,int>)(x=>x+1);
3.3 不能調用構造函數和靜態方法
dynamic s=new dynamic();//編譯錯誤 因為此時編譯器無法指定具體的類型。
3.4 類型聲明和泛型類型參數
不能將dynamic關鍵字用來基類聲明,也不能將dynamic用於類型參數的約束,或作為類型所實現的接口的一部分。具體的實例代碼如下:
1 class DynamicBaseType:dynamic //基類不能為dynamic類型
2 {
3 }
4
5 class DynamicTypeConstrain<T> where T:dynamic //dynamic類型不能用作類型參數
6 {
7 }
8
9 class DynamicInterface :IEnumerable<dynamic>//dynamic不能作為所實現接口的一部分
10 {
11 }
4、實現自己的動態行為
引入靜態類型後,我們便可為類型動態地添加屬性和方法,從而實現我們自己的動態行為。具體來說,.NET中支持3種實現動態行為的方式:
(1)使用ExpandoObject;
(2)使用DynamicObject;
(3)實現IDynamicMetaObjectProvider接口
4.1 使用ExpandoObject來實現動態行為
ExpandoObject是實現動態行為的最簡單的方式,其實現代碼如下:
1 using System;
2 using System.Dynamic;
3
4 namespace 動態類型
5 {
6 class Program
7 {
8 static void Main(string[] args)
9 {
10 dynamic expand = new ExpandoObject();
11 //動態綁定成員
12 expand.Name = "Helius";
13 expand.Age = 24;
14 expand.Addmethod = (Func<int, int>) (x => x + 1);
15 //調用類型成員
16 Console.WriteLine($"enpand類型的姓名為:{expand.Name},年齡為:{expand.Age}");
17 Console.WriteLine($"調用expand類型的動態幫東方法:{expand.Addmethod(5)}");
18 Console.ReadKey();
19 }
20 }
21 }
以上代碼通過使用ExpandoObject類,動態地為expand動態類型添加了屬性和函數,並且expand類型也可以成功地被調用。
4.2 使用DynamicObject來實現動態行為
除了使用ExpandoObject,我們還可以通過重寫DynamicObject來實現動態行為,具體的實現代碼如下:
1 using System;
2 using System.Dynamic;
3
4 namespace 動態類型
5 {
6 class Program
7 {
8 static void Main(string[] args)
9 {
10 dynamic dynamicobj=new DynamicType();
11 dynamicobj.CallMethod();
12 dynamicobj.Name = "Helius";
13 dynamicobj.Age = 24;
14 Console.ReadKey();
15 }
16 }
17
18
19 class DynamicType:DynamicObject
20 {
21 //重寫TryXXX方法,該方法表示對對象進行動態調用
22 public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
23 {
24 Console.WriteLine($"{binder.Name}方法正在被調用");
25 result = null;
26 return true;
27 }
28
29 public override bool TrySetMember(SetMemberBinder binder, object value)
30 {
31 Console.WriteLine($"{binder.Name} 屬性被設置,設置的值為{value}");
32 return true;
33 }
34 }
35 }
4.3 使用IDynamicMetaObjectProvider接口來實現動態行為
由於Dynamic類型是在運行過程中動態創建對象的,所以在對該類型的每個成員進行訪問時,都會首先調用GetMethodObject方法來獲得動態對象,然後再通過這個動態對象來完成調用。因為為了實現IDynamicMetaObjectProvider接口,我們需要先實現一個GetMetaObject方法,用以返回DynamicMetaObject對象。
1 using System;
2 using System.Dynamic;
3 using System.Linq.Expressions;
4
5 namespace 動態類型
6 {
7 class Program
8 {
9 static void Main(string[] args)
10 {
11
12 dynamic dynamicObj2=new DynamicType2();
13 dynamicObj2.Call();
14 Console.ReadKey();
15 }
16 }
17
18
19 class DynamicType:DynamicObject
20 {
21 //重寫TryXXX方法,該方法表示對對象進行動態調用
22 public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
23 {
24 Console.WriteLine($"{binder.Name}方法正在被調用");
25 result = null;
26 return true;
27 }
28
29 public override bool TrySetMember(SetMemberBinder binder, object value)
30 {
31 Console.WriteLine($"{binder.Name} 屬性被設置,設置的值為{value}");
32 return true;
33 }
34 }
35
36
37 internal class DynamicType2 : IDynamicMetaObjectProvider
38 {
39 public DynamicMetaObject GetMetaObject(Expression parameter)
40 {
41 Console.WriteLine("開始獲得元數據......");
42 return new Metadynamic(parameter, this);
43 }
44 }
45
46 internal class Metadynamic : DynamicMetaObject
47 {
48 internal Metadynamic(Expression expression, DynamicType2 value):base(expression,BindingRestrictions.Empty,value)
49 {
50 }
51
52 public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args)
53 {
54 //獲得真正的對象
55 DynamicType2 target = (DynamicType2) base.Value;
56 Expression self = Expression.Convert(base.Expression, typeof (DynamicType2));
57 var restrictions = BindingRestrictions.GetInstanceRestriction(self, target);
58 //輸出綁定方法名
59 Console.WriteLine($"{binder.Name} 方法被調用了");
60 return new DynamicMetaObject(self,restrictions);
61 }
62 }
63 }
結果為:
