了解 C# 4 中的 Dynamic 關鍵字

來源: microsoft  發布時間: 2011-03-06 21:24  閱讀: 5688 次  推薦: 1   原文鏈接   [收藏]  

  dynamic 關鍵字和動態語言運行時 (DLR) 是 C# 4 和 Microsoft .NET Framework 4 中的重大新增功能。 這些功能在宣布時就引起了人們的極大興趣,并伴隨著許多疑問。 同時人們也給出了很多答案,但這些答案現在已散布于各種文檔以及各種技術博客和文章之中。 這樣,人們在各種論壇和會議上總是一遍又一遍地提出相同的問題。

  本文全面概述了 C# 4 中新增的動態功能,并且深入探討了這些功能如何同其他語言和框架功能(例如反射或隱式類型化變量)一起使用。 鑒于已有大量信息可用,我有時會重新使用一些經典示例,并提供指向原始源的鏈接。 我還將提供指向相關內容的大量鏈接,供您進一步閱讀。

  什么是“動態”?

  編程語言有時可劃分為靜態類型化語言和動態類型化語言。 C# 和 Java 經常被認為是靜態類型化語言的例子,而 Python、Ruby 和 JavaScript 是動態類型化語言的例子。

  一般而言,動態語言不執行編譯時類型檢查,僅在運行時識別對象的類型。 這種方法有利有弊:代碼編寫起來往往更快、更容易,但同時,由于您不會獲得編譯器錯誤,只能通過單元測試和其他方法來確保應用程序正常運行。

  C# 最初是作為純靜態語言創建的,但 C# 4 添加了一些動態元素,用以改進與動態語言和框架之間的互操作性。 C# 團隊考慮了多種設計選項,但最終確定添加一個新關鍵字來支持這些功能:dynamic。

  dynamic 關鍵字可充當 C# 類型系統中的靜態類型聲明。 這樣,C# 就獲得了動態功能,同時仍然作為靜態類型化語言而存在。 若要了解為何以及如何做出了這樣的決定,請參考 PDC09 (microsoftpdc.com/2009/FT31) 上由Mads Torgersen 撰寫的演示文稿“C# 4 中的動態綁定”。 尤其是,動態對象被認定是 C# 語言中的“一等公民”,因此沒有用于打開或關閉動態功能的選項,并且沒有向 C# 添加過類似于 Visual Basic 中的 Option Strict On/Off 之類的功能。

  當您使用 dynamic 關鍵字時,您就告訴編譯器關閉編譯時檢查。 網上以及 MSDN 文檔中 (msdn.microsoft.com/library/dd264736) 有大量關于如何使用該關鍵字的示例。 下面是一個常見示例:

 

 
dynamic d = "test";
Console.WriteLine(d.GetType());

// Prints "System.String".
d = 100;
Console.WriteLine(d.GetType());

// Prints "System.Int32".     
 

  如您所見,可以將不同類型的對象分配給已聲明為 dynamic 的變量。 這段代碼會通過編譯,并在運行時確定對象的類型。 不過,下面的代碼也會通過編譯,但在運行時會引發異常:

 

 
dynamic d = "test";
// The following line throws an exception at run time.
d++;   
 

  原因是相同的:編譯器不知道該對象的運行時類型,因此無法告訴您遞增操作在此情況下不受支持。

  缺少編譯時類型檢查也會導致 IntelliSense 功能無效。 由于 C# 編譯器不知道對象的類型,因此它無法枚舉該對象的屬性和方法。 正如在用于 Visual Studio 的 IronPython 工具中那樣,通過附加的類型推斷可能會解決此問題,但目前 C# 不提供這種類型推斷。

  但是,在許多可能獲益于動態功能的方案中,由于代碼使用了字符串文本而導致 IntelliSense 還是不可用。 本文在后面將對這一問題進行更詳細的討論。

  Dynamic、Object 還是 Var?

  那么,dynamic、object 和 var 之間的實際區別是什么?何時應使用它們? 下面是每個關鍵字的簡短定義和一些示例。

  關鍵字 object 表示 System.Object 類型,它是 C# 類層次結構中的根類型。 此關鍵字經常在編譯時無法確定對象類型時使用,而這種情況經常在各種互操作性情形中發生。

  您需要使用顯式轉換將已聲明為 object 的變量轉換為特定類型:

 

 
object objExample = 10;
Console.WriteLine(objExample.GetType());
      
 

  顯然,這將輸出 System.Int32。 但是,因為靜態類型為 System.Object,所以您在這里需要一個顯式轉換:

 

 
objExample = (int)objExample + 10;       

  您可以賦予不同類型的值,因為它們都是從 System.Object 繼承的:

 

 
objExample = "test"        

  從 C# 3.0 起,關鍵字 var 開始用于隱式類型化局部變量以及匿名類型。 此關鍵字經常與 LINQ 結合使用。 當使用 var 關鍵字聲明變量時,將在編譯時根據初始化字符串推斷該變量的類型。 在運行時無法更改該變量的類型。 如果編譯器不能推斷類型,它會生成一個編譯錯誤:

 

 
var varExample = 10;
Console.WriteLine(varExample.GetType());
  
 

  這段代碼會輸出 System.Int32,與靜態類型相同。

  在下面的示例中,因為 varExample 的靜態類型為 System.Int32,所以不需要轉換:

 

 
varExample = varExample + 10;   
 

  下面一行不進行編譯,因為只能將整數賦給 varExample:

 

 
varExample = "test";   
 

  C# 4 中引入的 dynamic 關鍵字可使某些傳統上依賴于 object 關鍵字的情形更容易編寫和維護。 實際上,動態類型在后臺使用 System.Object 類型。但與 object 不同的是,動態類型不需要在編譯時執行顯式轉換操作,因為它僅在運行時識別類型:

 

 
dynamic dynamicExample = 10;
Console.WriteLine(dynamicExample.GetType());
    
 

  此段代碼會輸出 System.Int32。

  在下面這一行中不需要轉換,因為僅在運行時識別類型:

 

 
dynamicExample = dynamicExample + 10;  

  可以將不同類型的值賦給 dynamicExample:

 

 
dynamicExample = "test";

在 C# 常見問題解答博客 (bit.ly/c95hpl) 上,提供了關于關鍵字 object 和 dynamic 之間差別的詳細博客文章。

  有時會引起混淆的是,所有這些關鍵字可以一起使用,即它們不是互相排斥的。 例如,我們來看一看下面的代碼:

 

 
dynamic dynamicObject = new Object();
var anotherObject
= dynamicObject;  
 

  anotherObject 的類型是什么? 我的回答是:dynamic。 請記住,在 C# 類型系統中,dynamic 實際上是一個靜態類型,因此,編譯器將為 anotherObject 推斷此類型。 務必要知道,var 關鍵字不過是一個指令,它讓編譯器根據變量的初始化表達式推斷類型;var 不是類型。

  動態語言運行時

  說起 C# 語言環境中的“dynamic”這一術語,它通常指下面兩個概念之一:C# 4 中的 dynamic 關鍵字或 DLR。 雖然這兩個概念是相關的,但也務必要了解它們之間的差別。

  DLR 有兩個主要目的。 首先,它實現動態語言和 .NET Framework 之間的互操作。 其次,它將動態行為引入 C# 和 Visual Basic 之中。

  DLR 的創建吸取了構建 IronPython (ironpython.net) 時的經驗教訓(IronPython 是在 .NET Framework 上實現的第一種動態語言)。 在構建 IronPython 時,工作團隊發現他們可以針對多種語言重復使用他們的實現,因此,他們為 .NET 動態語言創建了一個公共基礎平臺。 與 IronPython 一樣,DLR 已成為一個開源項目,其源代碼目前在 dlr.codeplex.com 上提供。

  后來,.NET Framework 4 中也納入了 DLR,以支持 C# 和 Visual Basic 中的動態功能。 如果您只需要 C# 4 中的 dynamic 關鍵字,那么使用 .NET Framework 就可以了。在大多數情況下,僅憑 .NET Framework 即可處理與 DLR 之間的所有交互。 但是,如果您希望實現新的動態語言或將其遷移到 .NET,則可以獲益于開源項目中額外的幫助程序類,該開源項目為語言實現人員提供了更多功能和服務。

  在靜態類型化語言中使用 Dynamic

  我們并不期待每個人都盡可能使用動態而不是靜態類型聲明。 編譯時檢查是一個強大的工具,對它的使用多多益善。 而且,再次指出,C# 中的動態對象不支持 IntelliSense,這對總體工作效率可能會有些影響。

  同時,在出現 dynamic 關鍵字和 DLR 之前,有一些方案在 C# 中曾經難以實現。 在以前的大多數情況下,開發人員使用 System.Object 類型和顯式轉換,同樣不能很好地利用編譯時檢查和 IntelliSense。 下面是一些例子。

  人們最熟知的一個情況是,有時必須使用 object 關鍵字來實現與其他語言或框架的互操作性。 通常,您必須依靠反射來獲取對象的類型以及訪問其屬性和方法。 語法有時難以閱讀,因此代碼難以維護。 此時使用動態功能可能比使用反射更加容易和方便。

  Anders Hejlsberg 在 PDC08 (channel9.msdn.com/pdc2008/TL16) 上提供了一個極好的例子,如下所示:

 

 
object calc = GetCalculator();
Type calcType
= calc.GetType();
object res = calcType.InvokeMember(
"Add", BindingFlags.InvokeMethod,
null, new object[] { 10, 20 });
int sum = Convert.ToInt32(res);

  該函數返回一個計算器,但系統在編譯時不知道此計算器對象的精確類型。 代碼所依賴的唯一事情是此對象應具有 Add 方法。 請注意,此方法無法使用 IntelliSense,因為您以字符串文本的形式提供了方法名稱。

  使用 dynamic 關鍵字,代碼就很簡單了:

 

 
dynamic calc = GetCalculator();
int sum = calc.Add(10, 20);

假設情況沒有變化:存在某種我們希望其具有 Add 方法的未知類型的對象。 與上一個示例一樣,此方法也不能使用 IntelliSense。 但語法閱讀和使用起來要容易很多,看上去就像在調用一個普通的 .NET 方法。

  動態方法包

  可以利用動態功能的另外一個例子是創建動態方法包,動態方法包就是可在運行時添加和刪除屬性及方法的對象。

  .NET Framework 4 有一個新的命名空間:System.Dynamic。 此命名空間實際上是 DLR 的一部分。 System.Dynamic.ExpandoObject 和 System.Expando.DynamicObject 類與新的 dynamic 關鍵字相結合,有助于以清晰和易于閱讀的方式來創建動態結構和層次結構。

  例如,下面說明了如何使用 ExpandoObject 類來添加屬性和方法:

 

 
dynamic expando = new ExpandoObject();
expando.SampleProperty
= "This property was added at run time";
expando.SampleMethod
= (Action)(() => Console.WriteLine(expando.SampleProperty));
expando.SampleMethod();

  要了解更加深入的方案,您一定要看看關于 ExpandoObject 和 DynamicObject 類的 MSDN 文檔。 同時,還有一些值得一看的文章,比如由 Bill Wagner 撰寫的文章“動態方法包”(msdn.microsoft.com/library/ee658247) 以及 C# 常見問題解答博客文章“C# 4.0 中的 Dynamic:ExpandoObject 簡介”(bit.ly/amRYRw)。

  類包裝

  您可以為自己的庫提供更好的語法,或為現有庫創建包裝。 與前兩個方案相比,這是一個更高級的方案,并且需要對 DLR 具體內容有更深入的了解。

  對于簡單情況,可以使用 DynamicObject 類。 在這個類中,可以將方法和屬性的靜態聲明與動態調度進行混合。 這樣,您就可以在一個類屬性中存儲一個要為其提供更佳語法的對象,但通過動態調度來處理針對該對象的所有操作。

  例如,請看一下圖 1 中的 DynamicString 類,該類包裝了一個字符串,并在通過反射實際調用所有方法之前顯示這些方法的名稱。

 

 
public class DynamicString : DynamicObject {
string str;

public DynamicString(string str) {
this.str = str;
}


public override bool TryInvokeMember(
InvokeMemberBinder binder,
object[] args,
out object result) {
Console.WriteLine(
"Calling method: {0}", binder.Name);
try {
result
= typeof(string).InvokeMember(
binder.Name,
BindingFlags.InvokeMethod
|
BindingFlags.Public |
BindingFlags.Instance,
null, str, args);
return true;
}

catch {
result
= null;
return false;
}
}
}
 
 

  若要實例化該類,應使用 dynamic 關鍵字:

 

 
dynamic dStr = new DynamicString("Test");
Console.WriteLine(dStr.ToUpper());
Console.ReadLine();
   
 

  當然,這個特定示例出于演示目的而設計,不具有實際效率。 但是,如果您擁有已嚴重依賴于反射的 API,就可以如此處所示將所有通過反射進行的調用打包,以便針對 API 的最終用戶隱藏這些調用。

  有關更多示例,請參見 MSDN 文檔 (msdn.microsoft.com/library/system.dynamic.dynamicobject) 以及 C# 常見問題解答博客文章“C# 4.0 中的 Dynamic:通過 DynamicObject 創建包裝”(bit.ly/dgS3od)。

  如前所述,DynamicObject 類是由 DLR 提供的。 生成動態對象所需要的僅僅是 DynamicObject 或 ExpandoObject。 但是,某些動態對象具有用于訪問成員和調用方法的復雜綁定邏輯。 這種對象需要實現 IDynamicMetaObjectProvider 接口并提供其自己的動態調度。 這是一種高級方案,感興趣的讀者可以讀一下由 Bill Wagner 撰寫的文章“實現動態接口”(msdn.microsoft.com/vcsharp/ff800651),以及由 Alex Turner 及 Bill Chiles 撰寫的文章“庫創作者 DLR 入門”(dlr.codeplex.com)。

  可編寫腳本的應用程序

  腳本是向應用程序提供可擴展性的一種強大方法。 Microsoft Office 可作為這方面的一個好例子:由于 Visual Basic for Applications (VBA) 的存在,可以使用大量的宏、加載項和插件。 現在,DLR 提供了一組公用的語言宿主 API,因此可讓您創建可編寫腳本的應用程序。

  例如,您可以創建一個應用程序,使用戶能夠自己在其中添加功能而不需要主產品提供新功能,例如向游戲中添加新的字符和映射,或向業務應用程序添加新的圖表。

  您必須使用來自 dlr.codeplex.com 的開源版 DLR 而不是由 .NET Framework 4 使用的 DLR,因為 DLR 腳本編寫和宿主 API 現在僅在開源版中提供。 另外,假定的情況是您不是使用 C# 編寫腳本,而是使用一種 .NET 動態語言(如 IronPython 或 IronRuby)來編寫。 然而,實際上任何語言都可以支持這些 API,包括不是在 DLR 之上實現的語言。

  有關使用此功能的詳細信息,請觀看 PDC09 (microsoftpdc.com/2009/FT30) 上由 Dino Viehland 所做的演示“使用動態語言生成可編寫腳本的應用程序”。

  識別動態對象

  如何區分動態對象與其他對象? 一個簡便方法是使用內置的 IDE 功能。 您可以將鼠標光標懸停在對象上以查看其聲明類型,或檢查 IntelliSense 是否可用。

  然而在運行時,情況會變得更加復雜。 您無法檢查變量是否是通過 dynamic 關鍵字聲明的 — 動態對象的運行時類型是它所存儲的值的類型,您無法獲取其靜態類型聲明。 這種情況與將變量聲明為 object 時的情況相同:在運行時,您只能獲取變量所存儲的值的類型;無法判斷此變量最初是否聲明為 object。

  運行時所能確定的是對象是否來自 DLR。 知道這種情況可能十分重要,因為像 ExpandoObject 和 DynamicObject 類型的對象可在運行時改變其行為,例如,添加和刪除屬性及方法。

  此外,也無法使用標準反射方法來獲取有關這些對象的信息。 如果向 ExpandoObject 類的實例添加屬性,則無法通過反射獲取該屬性:

 

 
dynamic expando = new ExpandoObject();
expando.SampleProperty
= "This property was added at run time";
PropertyInfo dynamicProperty
= expando.GetType().GetProperty("SampleProperty");
// dynamicProperty is null.

  有利的方面是,在 .NET Framework 4 中,所有可動態添加和刪除成員的對象都必須實現一個特定接口:System.Dynamic.IDynamicMetaObjectProvider。 DynamicObject 和 ExpandoObject 類也實現了這個接口。 不過,這并不表示任何使用 dynamic 關鍵字聲明的對象都實現此接口:

 

 
dynamic expando = new ExpandoObject();
Console.WriteLine(expando
is IDynamicMetaObjectProvider);
// True
dynamic test = "test";
Console.WriteLine(test
is IDynamicMetaObjectProvider);
// False

因此,如果將動態功能與反射一起使用,則請記住,反射不適用于動態添加的屬性和方法,并且最好檢查正在反射的對象是否實現了 IDynamicMetaObjectProvider 接口。

  動態功能與 COM 互操作

  C# 團隊在 C# 4 版本中專門考慮的 COM 互操作方案是針對 Microsoft Office 應用程序(如 Word 和 Excel)進行編程。 他們的目的是讓這一任務在 C# 中變得像在 Visual Basic 中那樣容易和自然。 這也是 Visual Basic 和 C# 共同發展策略的一部分,這個策略旨在實現兩種語言的功能對等,并相互借鑒最佳、最具效率的解決方案。

  若需了解詳細信息,請參閱 Scott Wiltamuth 的 Visual Studio 博客文章“C# 和 VB 共同發展”(bit.ly/bFUpxG)。

  顯示了一段 C# 4 代碼,該代碼向 Excel 工作表的第一個單元格中添加一個值,然后向第一列應用 AutoFit 方法。 每行下面的注釋顯示了 C# 3.0 及更早版本的中的等效代碼。

 

 
// Add this line to the beginning of the file:
// using Excel = Microsoft.Office.Interop.Excel;

var excelApp = new Excel.Application();

excelApp.Workbooks.Add();

// excelApp.Workbooks.Add(Type.Missing);

excelApp.Visible = true;

Excel.Range targetRange
= excelApp.Range["A1"];
// Excel.Range targetRange = excelApp.get_Range("A1", Type.Missing);

targetRange.Value = "Name";
// targetRange.set_Value(Type.Missing, "Name");

targetRange.Columns[1].AutoFit();
// ((Excel.Range)targetRange.Columns[1, Type.Missing]).AutoFit();

  此示例有趣的地方是,您在代碼中的任何位置都看不到 dynamic 關鍵字。實際上,該關鍵字僅在下面一行中用到:

 

 
targetRange.Columns[1].AutoFit();
// ((Excel.Range)targetRange.Columns[1, Type.Missing]).AutoFit();

  在 C# 3.0 版中,targetRange.Columns[1, Type.Missing] 返回 object,這便是需要向 Excel.Range 轉換的原因。 但在 C# 4 和 Visual Studio 2010 中,這樣的調用將以靜默方式轉換為動態調用。 因此,C# 4 中 targetRange.Columns[1] 的類型實際上是 dynamic。

  另一個突出特點是,C# 4 中的 COM 互操作改進不僅限于 dynamic。 由于其他一些新增功能(例如索引屬性以及命名參數和可選參數),其他所有行中的代碼也有所改進。 由 Chris Burrows 撰寫的 MSDN 雜志 文章“.NET Framework 4 中的新增 C# 功能”(msdn.microsoft.com/magazine/ff796223) 中對這些新增功能做了很好的概述。

  從哪里可以獲取更多信息?

  希望本文已涵蓋您對 C# 4 中的 dynamic 關鍵字可能有的大部分疑問,但我確信本文并非面面俱到。 如果您有意見、問題或建議,敬請光臨 dlr.codeplex.com/discussions 提出。 其他人可能已經提過您關心的問題,或者您可以發起新的討論。 我們擁有一個活躍的社區,歡迎新成員加入。

1
0
 
標簽:.NET C#4.0 Dynamic
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()