程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C#4.0新特性(1):Dynamic Lookup 動態查找

C#4.0新特性(1):Dynamic Lookup 動態查找

編輯:C#入門知識

 


C# 4.0的主要主題是動態編程。對象的意義變得越來越“動態”,它們的結構和行為無法通過靜態類型來捕獲,或者至少編譯器在編譯程序時無法得知對象的結構和行為。例如——

•a. 來自動態編程語言——如Python或Ruby——的對象
•b. 通過IDispatch訪問的COM對象
•c. 通過反射訪問的一般.NET類型
•d. 結構發生過變化的對象——如HTML DOM對象
動態查找允許在編寫方法、運算符和索引器調用、屬性和字段訪問甚至對象調用時,繞過C#靜態類型檢查,而在運行時進行解析。
動態查找允許動態(即在運行時)實現對某個對象的操作與對象類型的綁定,而不管這個對象是來自COM,IronPython,HTML DOM還是CLR的反射。你可以在程序中繞過編譯器的類型檢查,而把類型的匹配(lookup)丟給運行時去作。如果你需要對這樣的對象進行操作,則會用到一個全新的類型:dynamic。

dynamic類型(The dynamic type)
C# 4.0引入了一個新的靜態類型,稱為dynamic。當你擁有了一個dynamic類型的對象後,你“對他做的事情”只會在運行時進行解析——

1 dynamic d = GetDynamicObject(...); 

2 d.M(7);
C#編譯器允許你使用任何參數在d上調用一個方法,因為它的類型是dynamic。運行時會檢查d的實際類型,並檢測在它上面“用一個int調用M”是什麼意思。

可以認為dynamic類型是object類型的一個特殊版本,指出了對象可以動態地使用。選擇是否使用動態行為很簡單——任何對象都可以隱式轉換為dynamic,“掛起信任”直到運行時。反之,從dynamic到任何其他類型都存在“賦值轉換”,可以類似於賦值的結構中進行隱式轉換——

1 dynamic d = 7; // implicit conversion 

2 int i = d; // assignment conversion
動態操作(Dynamic operations)
不僅是方法調用,字段和屬性訪問、索引器和運算符調用甚至委托調用都可以動態地分派——

1 dynamic d = GetDynamicObject(…); 

2 d.M(7); // calling methods 

3 d.f = d.P; // getting and settings fields and properties 

4 d[“one”] = d[“two”]; // getting and setting thorugh indexers 

5 int i = d + 3; // calling operators 

6 string s = d(5,7); // invoking as a delegate
01 class Program

02 {

03     static void Main(string[] args)

04     {

05         dynamic dyn = 1;

06         object obj = 1;

07  

08         // Rest the mouse pointer over dyn and obj to see their

09         // types at compile time.

10         System.Console.WriteLine(dyn.GetType());

11         System.Console.WriteLine(obj.GetType());

12     }

13 }
程序輸出為:

1 System.Int32

2  

3 System.Int32
dynamic關鍵字還可以作為類型的屬性、字段、索引器、參數、返回值、局部變量和類型約束。請看下面示例:

01 class ExampleClass

02 {

03     // A dynamic field.

04     static dynamic field;

05  

06     // A dynamic property.

07     dynamic prop { get; set; }

08  

09     // A dynamic return type and a dynamic paramater type.

10     public dynamic exampleMethod(dynamic d)

11     {

12         // A dynamic local variable.

13         dynamic local = "Local variable";

14         int two = 2;

15  

16         if (d is int)

17         {

18             return local;

19         }

20         else

21         {

22             return two;

23         }

24     }

25 }
還可以作為顯式類型轉換中的目標類型,如下:

01 static void convertToDynamic()

02 {

03     dynamic d;

04     int i = 20;

05     d = (dynamic)i;

06     Console.WriteLine(d);

07  

08     string s = "Example string.";

09     d = (dynamic)s;

10     Console.WriteLine(d);

11  

12     DateTime dt = DateTime.Today;

13     d = (dynamic)dt;

14     Console.WriteLine(d);

15  

16 }

17 // Results:

18 // 20

19 // Example string.

20 // 2/17/2009 9:12:00 AM
還可以用於is、as和typeof運算符:

01 int i = 8;

02 dynamic d;

03 // With the is operator.

04 // The dynamic type behaves like object. The following

05 // expression returns true unless someVar has the value null.

06 if (someVar is dynamic) { }

07  

08 // With the as operator.

09 d = i as dynamic;

10  

11 // With typeof, as part of a constructed type.

12 Console.WriteLine(typeof(List<dynamic>));

13  

14 // The following statement causes a compiler error.

15 //Console.WriteLine(typeof(dynamic));
類型轉換:

1 dynamic d1 = 7;

2 dynamic d2 = "a string";

3 dynamic d3 = System.DateTime.Today;

4 dynamic d4 = System.Diagnostics.Process.GetProcesses();
1 int i = d1;

2 string str = d2;

3 DateTime dt = d3;

4 System.Diagnostics.Process[] procs = d4;
完整示例如下:

01 using System;

02  

03 namespace DynamicExamples

04 {

05     class Program

06     {

07         static void Main(string[] args)

08         {

09             ExampleClass ec = new ExampleClass();

10             Console.WriteLine(ec.exampleMethod(10));

11             Console.WriteLine(ec.exampleMethod("value"));

12  

13             // The following line causes a compiler error because exampleMethod

14             // takes only one argument.

15             //Console.WriteLine(ec.exampleMethod(10, 4));

16  

17             dynamic dynamic_ec = new ExampleClass();

18             Console.WriteLine(dynamic_ec.exampleMethod(10));

19  

20             // Because dynamic_ec is dynamic, the following call to exampleMethod

21             // with two arguments does not produce an error at compile time.

22             // However, itdoes cause a run-time error.

23             //Console.WriteLine(dynamic_ec.exampleMethod(10, 4));

24         }

25     }

26  

27     class ExampleClass

28     {

29         static dynamic field;

30         dynamic prop { get; set; }

31  

32         public dynamic exampleMethod(dynamic d)

33         {

34             dynamic local = "Local variable";

35             int two = 2;

36  

37             if (d is int)

38             {

39                 return local;

40             }

41             else

42             {

43                 return two;

44             }

45         }

46     }

47 }

48 // Results:

49 // Local variable

50 // 2

51 // Local variable
運行時查找(Runtime lookup)
在運行時,動態操作將根據目標對象d的本質進行分派——

COM對象(COM objects)
如果d是一個COM對象,則操作通過COM IDispatch進行動態分派。這允許調用沒有主互操作程序集(Primary Interop Assembly,PIA)的COM類型,並依賴C#中沒有對應概念的COM特性,如索引屬性和默認屬性。

下面的代碼動態的獲取一個 COM 對象類型,並通過該類型創建這個 COM 對象的實例,並准備調用該實例上的一個方法實現我們需要的功能。這個例子引用了 Speech API 中的 SAPI.SpVoice 對象,並調用了其 Speak() 方法。

using System;

Type type = Type.GetTypeFromProgID("SAPI.SpVoice");
dynamic spVoice = Activator.CreateInstance(type);
spVoice.Speak("Hello, C# 4.0!");編譯並運行此示例,我們通過計算機的音箱得到了正確的語音。

現在我們開始思考一個問題,spVoice.Speak(string) 這個函數簽名其實在設計時以及編譯時對於 C# 編譯器來說都是未知的。因為 spVoice 變量的類型是 System.__ComObject,並且由於 SAPI.SpVoice 對應的 ProgID 指向一個非托管的 COM 對象,加上我們並沒有導入任何 TLB 庫,因此,Speak() 方法只能是在運行時由編譯器自動綁定。

這就是動態查找最基本的意圖,它的提出是為了在 Office 以及 COM 互操作性編程中更加簡化代碼。dynamic 變量的所有運行時類型信息都直接由運行時上下文綁定並執行(稱之為“晚期綁定”),這些工作通過 System.Dynamic.RuntimeBinding 類完成。

動態對象(Dynamic objects)
如果d實現了IDynamicObject接口,則請求d自身來執行該操作。因此通過實現IDynamicObject接口,類型可以完全重新定義動態操作的意義。這在動態語言——如IronPython和IronRuby——中大量使用,用於實現他們的動態對象模型。API也會使用這類對象,例如HTML DOM允許直接使用屬性語法來訪問對象的屬性。

簡單對象(Plain objects)
除此之外,則d是一個標准的.NET對象,操作是通過在其類型上進行反射來分派的,C#的“運行時綁定器(runtime binder)”實現了運行時的C#查找和重載解析。其背後的本質是將C#編譯器作為運行時組件運行,來“完成”被靜態編譯器延遲的動態操作。

考慮下面的代碼——

1 dynamic d1 = new Foo(); 

2 dynamic d2 = new Bar(); 

3 string s; 

4  

5 d1.M(s, d2, 3, null);
由於對M進行調用的接受者是dynamic類型的,C#編譯器不會試圖解析該調用的意義。而是將有關該調用的信息存儲起來,供運行時使用。該信息(通常稱作“有效載荷”)本質上等價於——

“使用下面的參數執行一個稱作M的實例方法——

•1. 一個string
•2. 一個dynamic
•3. 一個int字面值3
•4. 一個object字面值null”
在運行時,假設d1的實際類型Foo不是COM類型,也沒有實現IDynamicObject。在這種情況下,C#運行時綁定器擔負起了重載解析的工作,這是基於運行時類型信息完成的,按照下面的步驟進行處理——

•1. 使用反射獲取兩個對象d1和d2的實際運行時類型,它們沒有靜態類型(包括靜態類型dynamic)。結果為d1是Foo類型而d2是Bar。
•2. 使用普通的C#語義在Foo類型上對M(string,Bar,3,null)調用進行方法查找和重載解析。
•3. 如果找到了該方法,則調用它;否則拋出運行時異常。
帶有動態參數的重載解析(Overload resolution with dynamic arguments)
1 // 可以編譯,也可以運行

2 ec.exampleMethod2("a string");

3  

4 // 下面的代碼編譯可以通過,但是運行時將拋出運行時異常

5 ec.exampleMethod2(d1);

6 // 下面的代碼編譯將無法通過

7 //ec.exampleMethod2(7);
即便方法調用的接受者是靜態類型的,重載解析依然發生在運行時。當一個或多個實參是dynamic類型時就會出現這種情況——

1 Foo foo = new Foo(); 

2 dynamic d = new Bar(); 

3  

4 var result = foo.M(d);
C#運行時綁定器會基於d的運行時類型——也就是Bar——在Foo上M方法的靜態可知(statically known)重載之間進行選擇。其結果是dynamc類型。

動態語言運行時(The Dynamic Language Runtime)
動態語言運行時(Dynamic Language Runtime,DLR)是動態查找的底層實現的一個重要組件,也是.NET 4.0中新增的API。DLR不僅為C#動態查找,還為很多其他.NET上的動態語言——如IronPython和IronRuby——的實現提供了底層的基礎設施。這一通用基礎設施確保了高度的互操作性,更重要的是,DLR提供了卓越的緩存機制,使得運行時分派的效率得到巨大的改善。

對於使用C#動態查找的用戶來說,除了更高的性能之外,根本感覺不到DLR的存在。不過,如果你希望實現自己的動態分派對象,可以使用IDynamicObject接口來與DLR互操作,並向其中插入自己的行為。這是一個非常高級的任務,要求對DLR的內部工作原理有相當深入的了解。對於編寫API的人,值得在這些問題上花些功夫,這樣能夠更廣泛地改善可用性,例如為一個本身就是動態的領域編寫類庫。

已知問題(Open issues)
•DLR允許從一個表示類的對象創建對象。然而,C#的當前實現還不具備支持這一功能的語法。
•動態查找不能查找擴展方法。不論擴展方法是否依賴該調用的靜態上下文(也就是出現了using語句),因為該上下文信息並不會作為有效載荷的一部分保留下來。
•匿名函數(也就是lambda表達式)不能作為實參傳遞給動態方法調用。在不知道要轉換成什麼類型的情況下,編譯器不能綁定(也就是“理解”)一個匿名函數。
這些限制導致的結果就是很難在動態對象上使用LINQ查詢——

1 dynamic collection = ...; 

2  

3 var result = collection.Select(e => e + 5);
如果Selected方法是個擴展方法,動態查找將找不到它。即便它是一個實例方法,上面的代碼也無法編譯,因為lambda表達式不能作為參數傳遞給動態操作。

在C# 4.0中沒有計劃解決這些限制。

注意:dynamic 不同於 object。object 是任何類的基礎類,它是一個強類型的 CLR 類型,是引用類型。它具備完整的運行時信息,任何企圖訪問 object 不存在的方法簽名或者屬性都會被編譯時由編譯器檢查出來並報告錯誤。dynamic 類型在編譯時可能是未知的,所有針對它類型上的調用在編譯時是不會報告錯誤的,只有在運行時由 DLR 去檢查這些信息後,才能確定一個調用是否成功或者失敗。

由於 dynamic 的這些特性,使得它廣泛用於 COM 以及其他互操作編程中,它為我們的代碼帶來了簡化和便利,但同時由於這個口子已經被打開,所有強類型都可以被轉化為 dynamic,所以它如果被濫用,則會對代碼的維護難度有很大的提高。因為編譯器這個時候並不能在編譯時檢查更多的錯誤了。所以我們強烈建議您除了在互操作方面應用 dynamic 外,不要在其他地方濫用,以免對現有代碼產生潛在的維護成本。

 


 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved