大多數為 Microsoft .NET Framework 編寫的代碼都是基于靜態類型化的,盡管 .NET 通過反射支持動態類型化。此外,如同 Visual Basic 一樣,JScript 10 年前也在 .NET 基礎上擁有一個動態類型系統。靜態類型化意味著每個表達式都屬于一個已知的類型。類型和賦值在編譯時均經過驗證,因此大多數可能的類型化錯誤都會被提前發現。 有一個眾所周知的例外,那就是當您嘗試在運行時執行類型轉換時,如果源類型與目標類型不兼容,有時可能會導致動態錯誤。 靜態類型化性能良好、清晰明了,但這是建立在您事先對您的代碼(和數據)近乎完全了解這樣的假設之上的。現在,大家強烈希望能夠將這個限制放寬一點。超越靜態類型化通常意味著面臨三種截然不同的選擇:動態類型化、動態對象以及間接或基于反射的編程。 在 .NET 編程中,從 .NET Framework 1.0 開始就具備了反射功能,并且已經廣泛應用以推動特殊框架的發展,例如控制反轉 (IoC) 容器。這些框架用于解決運行時的類型依賴關系,從而使您的代碼能夠直接使用接口,而無需了解對象之后的具體類型及其實際行為。使用 .NET 反射,您能夠實現各種間接編程,以便您的代碼能夠與中間對象進行通信,然后由后者調度對固定接口的調用。您可以通過字符串的形式傳遞要調用的成員名稱,從而使您具有能夠從某些外部源讀取該名稱的靈活性。目標對象的接口是固定不變的 - 在您通過反射執行的任何調用后面,總是會有一個眾所周知的接口。? 動態類型化意味著您編譯的代碼將會忽略可以在編譯時檢測到的靜態類型結構。實際上,動態類型化把所有的類型檢查都推遲到了運行時進行。您編寫代碼時使用的接口仍然是固定不變的,但是您使用的值在不同的時刻可能會返回不同的接口。 .NET Framework 4 引入了一些能夠超越靜態類型的新功能。我在 ? 2010 年 5 月刊 中介紹了新的 dynamic 關鍵字。在本文中,我會探討對動態定義的類型(例如 Expando 對象)和動態對象的支持。通過動態對象,您可以通過編程的方式定義類型的接口,而不必通過靜態存儲在某些程序集中的定義來讀取它。動態對象結合了靜態類型化對象的正式清潔度和動態類型的靈活性。 動態對象方案動態對象的目標并不是要取代高質量的靜態類型。在可預見的將來,靜態類型仍然會保留在軟件開發的基礎中。通過靜態類型化,您可以在編譯時可靠地查找類型錯誤并生成代碼;而且正因為如此,生成的代碼無需在運行時進行檢查,運行速度更快。此外,略過編譯步驟的需求使得開發人員和架構師必須在設計軟件和定義交互層的公共接口時格外小心。 然而,還是有這樣的情況,您要通過編程的方式使用結構相對良好的數據塊。理想情況下,您希望這些數據由對象提供。但是相反,無論您是通過網絡連接獲得還是從磁盤文件讀取這些數據,您都是以純數據流的形式收到的。您有兩種選擇來使用這些數據:使用間接方法或使用專門類型。 在第一種情況下,您需要采用泛型 API 作為代理,并為您安排查詢和更新。在第二種情況下,您會有一個專門的類型,能夠完美地為您處理的數據建模。問題是,誰來創建這樣一個專門的類型? 在 .NET Framework 的某些部分中,已經有了一些很好的內部模塊示例,示范如何為特定的數據塊創建專門類型。一個明顯的示例就是 ASP.NET Web 窗體。當您發出關于 ASPX 資源的請求時,Web 服務器會檢索 ASPX 服務器文件的內容。該內容隨后被加載到一個字符串中,以便在 HTML 響應中進行處理。這樣您就有了一段結構相對良好的文本可供使用。 若要對此數據進行操作,您需要了解您對服務器控件有哪些引用,然后妥善實例化這些引用并將它們鏈接到一個頁面中。這些完全能夠通過為每個請求使用一個基于 XML 的解析程序來實現。但如果這么做,您就需要為每個請求額外付出解析程序的成本,而這項成本有可能是不可接受的。 考慮到因解析數據而額外增加的成本,ASP.NET 團隊決定引入一個一次性步驟,將標記解析到一個類中,而使該類能夠動態編譯。這樣,通過從 Web 窗體頁的代碼隱藏類派生出的一個專門類,將使用一段類似以下代碼的簡單標記:
?
<html> <head runat="server"> <title></title> </head> <body> <form id="Form1" runat="server"> <asp:TextBox runat="server" ID="TextBox1" /> <asp:Button ID="Button1" runat="server" Text="Click" /> <hr /> <asp:Label runat="server" ID="Label1"></asp:Label> </form> </body> </html> 圖 1 ? 顯示了由標記創建出來的類的運行時結構。灰色的方法名稱指的是內部過程,用于將帶有 runat=server 元素的元素解析到服務器控件的實例中。
圖 1 ? 動態創建的 Web 窗體類的結構 您可以將此方法應用到幾乎任何情況中,只要您的應用程序需要反復接收外部數據以進行處理。以流入應用程序的 XML 數據流為例。有多種 API 可以處理 XML 數據,范圍從 XML DOM 到 LINQ-to-XML。在任何情況下,您都必須通過查詢 XML DOM 或 LINQ-to-XML API 進行間接處理,或使用相同的 API 將原始數據解析到專門對象中。 在 .NET Framework 4 中,動態對象提供了替代方法 - 一個更簡單的 API,可基于部分原始數據動態創建類型。以下 XML 字符串是一個簡單的示例:
?
<Persons> <Person> <FirstName> Dino </FirstName> <LastName> Esposito </LastName> </Person> <Person> <FirstName> John </FirstName> <LastName> Smith </LastName> </Person> </Persons> 在 .NET Framework 3.5 中,若要將其轉換為可編程的類型,您可能要使用類似 圖 2 ? 中的代碼。 圖 2 ? 使用 LINQ-to-XML 將數據加載到 Person 對象中
?
var persons = GetPersonsFromXml(file); foreach(var p in persons) Console.WriteLine(p.GetFullName()); // Load XML data and copy into a list object var doc = XDocument.Load(@"..\..\sample.xml"); public static IList<Person> GetPersonsFromXml(String file) { var persons = new List<Person>(); var doc = XDocument.Load(file); var nodes = from node in doc.Root.Descendants("Person") select node; foreach (var n in nodes) { var person = new Person(); foreach (var child in n.Descendants()) { if (child.Name == "FirstName") person.FirstName = child.Value.Trim(); else if (child.Name == "LastName") person.LastName = child.Value.Trim(); } persons.Add(person); } return persons; } 此代碼使用 LINQ-to-XML 將原始內容加載到 Person 類的一個實例中:
?
public class Person { public String FirstName { get; set; } public String LastName { get; set; } public String GetFullName() { return String.Format("{0}, {1}", LastName, FirstName); } } .NET Framework 4 提供了一個不同的 API 來實現相同的目的。此 API 是新的 ExpandoObject 類的中心,更容易編寫,而且不需要您規劃、編寫、調試、測試和維護 Person 類。讓我們深入探討一下 ExpandoObject。 使用 ExpandoObject 類Expando 對象不是為 .NET Framework 發明的,事實上,它們比 .NET 還早出現幾年。我第一次聽到有人用這個術語來描述 JScript 對象是在 20 世紀 90 年代中期。Expando 是一種可擴充的對象,其結構完全是在運行時定義的。在 .NET Framework 4 中,您像使用傳統的托管對象一樣使用 Expando,不同之處在于其結構不在任何程序集外讀取,而完全是動態構建的。 Expando 對象非常適合對動態變化的信息進行建模,例如配置文件的內容。讓我們看看如何使用 ExpandoObject 類來存儲前述 XML 文檔的內容。 圖 3 ? 中顯示了完整的源代碼。 圖 3 ? 使用 LINQ-to-XML 將數據加載到 Expando 對象中
?
public static IList<dynamic> GetExpandoFromXml(String file) { var persons = new List<dynamic>(); var doc = XDocument.Load(file); var nodes = from node in doc.Root.Descendants("Person") select node; foreach (var n in nodes) { dynamic person = new ExpandoObject(); foreach (var child in n.Descendants()) { var p = person as IDictionary<String, object>); p[child.Name] = child.Value.Trim(); } persons.Add(person); } return persons; } 函數將返回一系列動態定義的對象。通過 LINQ-to-XML,您可以解析出標記中的節點,并為每個節點創建一個 ExpandoObject 實例。<Person> 下的每個節點的名稱都會成為 Expando 對象的一個新屬性。屬性值是節點的內部文字。基于 XML 內容,您得到了一個 Expando 對象,其 FirstName 屬性設置為 Dino。 然而,在 圖 3 ? 中,您可以看到一個索引器語法,用于填充 Expando 對象。這還需要做進一步的解釋。 在 ExpandoObject 類中ExpandoObject 類位于 System.Dynamic 命名空間中,在 System.Core 程序集中定義。ExpandoObject 代表一個對象,該對象的成員可以在運行時動態添加或刪除。該類是密封的,并且可以實現多個接口:
?
public sealed class ExpandoObject : IDynamicMetaObjectProvider, IDictionary<string, object>, ICollection<KeyValuePair<string, object>>, IEnumerable<KeyValuePair<string, object>>, IEnumerable, INotifyPropertyChanged; 正如您看到的,該類使用了多個可枚舉接口,包括 IDictionary<String, Object> 和 IEnumerable,來提供其內容。此外,它還實現了 IDynamicMetaObjectProvider。這是一個標準接口,能夠讓某個對象在動態語言運行時 (DLR) 內由依照 DLR 互操作性模型編寫的程序共享。換句話說,只有實現 IDynamicMetaObjectProvider 接口的對象能夠跨 .NET 動態語言共享。例如,Expando 對象可被傳遞到 IronRuby 這樣的組件。如果使用常規的 .NET 托管對象,就無法輕易做到這一點。或許您可以,但不會獲得動態行為。 ExpandoObject 類還實現了 INotifyPropertyChanged 接口。這樣,添加或修改成員時,該類就會引發一個 PropertyChanged 事件。支持 INotifyPropertyChanged 接口對于在 Silverlight 和 Windows Presentation Foundation 應用程序前端使用 Expando 對象至關重要。 創建 ExpandoObject 實例的方法與創建其他任何 .NET 對象一樣,只是存儲實例的變量是 dynamic 類型的:
?
dynamic expando = new ExpandoObject(); 此時,為 Expando 對象添加一個屬性只是為它分配了一個新的值,如下所示:
?
expando.FirstName = "Dino"; 即使沒有任何關于 FirstName 成員及其類型或可見性的信息,也沒有關系。這是一個動態代碼。正是因為這個原因,如果您使用 var 關鍵字將 ExpandoObject 實例分配給一個變量,結果會大為不同:
?
var expando = new ExpandoObject(); 此代碼的編譯和運行都會很正常。但是,根據這個定義,您不允許為 FirstName 屬性分配任何值。System.Core 中定義的 ExpandoObject 類沒有這樣的成員。更準確地說,ExpandoObject 類沒有任何公共成員。 這一點很關鍵。當 Expando 對象的靜態類型為 dynamic 時,操作就會綁定為動態操作,包括查找成員。當靜態類型為 ExpandoObject 時,操作就會綁定為普通的編譯時成員查找。因此,編譯器知道 dynamic 是一個特殊類型,但是不知道 ExpandoObject 是一個特殊類型。 在 圖 4 ? 中,您可以看到當 Expando 對象被聲明為 dynamic 類型,以及它被當作純 .NET 對象時,不同的 Visual Studio 2010 智能感知選項。在后一種情況中,智能感知顯示了默認的 System.Object 成員,以及集合類的擴展方法列表。
圖 4 ? Visual Studio 2010 智能感知和 Expando 對象 還應注意,在有些情況下,某些商用工具還會提供更多行為。 圖 5 ? 顯示了 ReSharper 5.0,該工具用于捕獲對象中當前定義的成員列表。如果成員是通過索引器以編程方式添加的,就不會發生這種情況。
圖 5 ? ReSharper 5.0 智能感知與 Expando 對象 若要向 Expando 對象添加方法,只需將其定義為屬性,除非您使用 Action<T> 或 Func<T> 委托來表達行為。例如:
?
person.GetFullName = (Func<String>)(() => { return String.Format("{0}, {1}", person.LastName, person.FirstName); }); 方法 GetFullName 會返回一個字符串,該字符串是通過將 Expando 對象中假設存在的姓和名屬性合并起來獲得的。如果您嘗試訪問 Expando 對象中缺少的成員,將會收到 RuntimeBinderException 異常。? 由 XML 驅動的程序為了讓您綜合理解到目前為止我所講過的概念,讓我為您介紹一個示例,其中數據結構和 UI 結構均在 XML 文件中定義。文件內容被解析到一系列 Expando 對象中,并由應用程序處理。但是,應用程序只處理動態形式的信息,也并未綁定到任何靜態類型。 圖 3 ? 中的代碼定義了一系列動態定義的 person Expando 對象。正如您期望的,如果向 XML 架構中添加一個新節點,就會在 Expando 對象中創建一個新屬性。如果您需要從外部源讀取成員的名稱,應當使用索引器 API 將其添加到 Expando 中。ExpandoObject 類顯式實現了 IDictionary<String, Object> 接口。這意味著您需要將 ExpandoObject 接口從字典類型中隔離,以便使用索引器 API 或 Add 方法:
?
(person as IDictionary<String, Object>)[child.Name] = child.Value; 由于這個行為,您只需要編輯 XML 文件來提供另一個數據集。但是,您如何才能使用這種動態變化的數據呢?您的 UI 需要足夠靈活,以便接受一組變化的數據。 讓我們舉一個簡單的示例。在這個示例中,您需要做的就是通過控制臺顯示數據。假設 XML 文件包含一個部分,用于描述期望的 UI(不管這在上下文中意味著什么)。例如,下面是我的代碼:
?
<Settings> <Output Format="{0}, {1}" Params="LastName,FirstName" /> </Settings> 此信息將會通過以下代碼加載到另一個 Expando 對象中:
?
dynamic settings = new ExpandoObject(); settings.Format = node.Attribute("Format").Value; settings.Parameters = node.Attribute("Params").Value; 主要過程將具備以下結構:
?
public static void Run(String file) { dynamic settings = GetExpandoSettings(file); dynamic persons = GetExpandoFromXml(file); foreach (var p in persons) { var memberNames = (settings.Parameters as String). Split(','); var realValues = GetValuesFromExpandoObject(p, memberNames); Console.WriteLine(settings.Format, realValues); } } Expando 對象包含輸出的格式,以及要顯示其值的成員的名稱。對于給定的 person 動態對象,您需要使用類似以下的代碼加載指定成員的值:
?
public static Object[] GetValuesFromExpandoObject( IDictionary<String, Object> person, String[] memberNames) { var realValues = new List<Object>(); foreach (var m in memberNames) realValues.Add(person[m]); return realValues.ToArray(); } 因為 Expando 對象實現了 IDictionary<String, Object>,您可以使用索引器 API 來獲得和設置值。 最后,從 Expando 對象檢索到的一系列值將會傳遞到控制臺以供實際顯示。 圖 6 ? 顯示了示例控制臺應用程序的兩個屏幕,其中的區別僅僅是基礎 XML 文件的結構不同。
圖 6 ? 由一個 XML 文件驅動的兩個示例控制臺應用程序 不可否認,這個示例非常簡單,但是它的實現機制與更有意思的示例是相似的。請試一試并向我們提供反饋! |
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
