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

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