本文章是由機器翻譯。

技術最前線

C# 4.0 中的 Expando 物件

Dino Esposito

下載程式碼範例

大部分的程式碼撰寫的 Microsoft.NET Framework 以靜態輸入,雖然.NET 支援動態的輸入,透過反映。此外,JScript 有.NET 10 的頂端的動態型別系統多年前,如 Visual Basic 做。靜態鍵入,表示每個運算式是已知的型別。在編譯時間及大部分的打字錯誤在事先找出的可能的型別和工作分派必須經過驗證。

已知的例外是當您嘗試在如果來源型別不相容於目標型別可能有時候會動態的錯誤中的執行階段型別轉換。

靜態輸入是很好的效能及清晰度,但根據您知道幾乎所有關於您的程式碼 (和資料) 的假設事先。今天,有 ’s 增強式需要有點休閒此條件約束。持續超過靜態輸入通常表示查看三個不同的選項:動態鍵入、 動態的物件和間接或反映式的程式設計。

.NET 程式設計,在.NET Framework 1.0 後已經可以使用反映,和已被廣泛採用燃料特殊架構,像反轉控制 (IoC) 容器來。這些架構處理因此啟用工時與介面,而不需要知道具象型別物件和其實際行為背後的程式碼執行階段解析型別相依性。您可以使用 [.NET 反映實作形式的間接的程式設計,您的程式碼位置交談中間的物件,將依序呼叫分派至固定的介面。您傳遞因此授予您自己讀取某些外部來源的彈性來叫用一個的字串為成員的名稱。目標物件的介面是固定且不變 (’s 永遠已知的介面背後透過反映您放入的任何呼叫。

動態鍵入時,表示您已編譯的程式碼會忽略靜態結構可以偵測編譯時期的型別。在就其實動態輸入延遲任何輸入到執行階段才檢查。介面碼對是仍是固定且不變,但是您所使用的值可能會傳回不同的介面,在不同的時間。

.NET Framework 4 推出一些新功能,讓您不再拘限於靜態型別。我涵蓋新的動態關鍵字在 的 月 2010年問題。這個本文中,我瀏覽動態定義的型別,例如 expando 物件和動態物件的支援。您可以使用動態的物件定義介面,以程式設計的方式代替讀取從以靜態方式儲存在某些組件中定義的型別。動態物件星期三靜態型別物件的型式的 cleanliness,具有彈性的動態型別。

動態物件的案例

動態物件不會在這裡取代靜態型別的好的品質。靜態型別,且會保持預見未來在軟體開發的基礎。靜態輸入文字可以可靠地尋找在編譯時期的型別錯誤,並產生程式碼,有鑑於此,可用的執行階段檢查且執行速度較快。在就另外需要傳遞編譯步驟會引導開發人員和架構設計人員,負責在軟體設計與相互作用的圖層的公用介面的定義中。

有,不過,有相當的組織完善的區塊,若要以程式設計方式使用資料的情況。在理想的情況下,您愛已經透過物件公開此資料。但是,是否到達您的網路連線,或從磁碟檔案中讀取,您收到它為一般的資料流。您有兩個選項来使用這項資料對:使用間接的方法,或使用臨機操作的型別。

在第一個的情況下,您會採用泛用的 API,做為 Proxy,並為您排列查詢和更新。在第二個的情況下,您會有完全的模型使用的資料的特定型別。但問題是誰將建立這類臨機操作型別?

在某些區段.NET Framework 中已經有建立臨機操作的型別,為特定的資料區塊內部的模組的很好的範例。其中一個明顯的範例是 ASP.NET Web Form。當您將 ASPX 資源的要求時,Web 伺服器擷取 ASPX 伺服器檔案的內容。該內容然後載入到 HTML 回應中處理的字串。因此,您有一段相當組織完善的工作的文字。

若要進行此資料,您必須瞭解什麼參考伺服器的控制項,您必須將它們正確執行個體化和其一起連結到網頁。這可以明確地完成每個要求使用以 XML 為基礎的剖析器。在如此做時,不過,您最後得到支付額外的成本可能是無法接受的成本的每一個要求的剖析器。

由於要剖析資料的這些額外成本,ASP.NET 團隊決定引入一次的步驟來剖析標記成動態編譯的類別。結果為標記,像這樣的簡單的區塊會消耗透過衍生自程式碼後置類別的 Web Form 網頁的臨機操作類別:

<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 項目參照到的伺服器控制項的執行個體 = 伺服器項目。

圖 1 的動態建立的 Web Form 類別的結構

幾乎任何應用程式用來接收處理重複的外部資料的情況,您可以套用這個方法。就例如考慮排至應用程式的 XML 資料的資料流。有幾個可用來處理 XML 的資料從 LINQ 以 XML DOM 的 API。在任何情況下您必須藉由查詢 XML DOM 或 XML LINQ 的 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--XML 載入資料到某物件

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,XML 將未經處理的內容載入某類別的執行個體:

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,以達到相同的效果。 置中對齊新 ExpandoObject 類別上,此 API 是更直接寫入,而且 doesn’t 需要您計劃、 撰寫、 偵錯、 測試和維護人員類別。 let’s 了解更多關於 ExpandoObject。

使用 ExpandoObject 類別

Expando 物件而不是針對.NET Framework ; 在就其實它們出現幾年來,.NET 之前。 首先,我聽一詞用來描述在中 1990 JScript 物件。 一個 expando 是物件的一種 inflatable 其結構完全在執行階段中定義。 在 [.NET] Framework 4 使用它它就像傳統的 Managed 的物件,但其結構不會讀取任何的組件之外,但完全以動態方式建立。

expando 物件是以動態方式變更資訊,例如內容的組態檔的模型的理想。 let’s 看到如何使用 ExpandoObject 類別來儲存先前提到的 XML 文件的內容。 的 圖 3 顯示完整的原始程式碼。

圖 3 使用 LINQ--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 以 XML 剖析出在標記中節點和為每個建立 ExpandoObject 執行個體。 每個節點 <Person> 下方的名稱會變成 expando 物件上的新屬性。 屬性的值是節點的內部文字。 根據 XML 內容,您最後有 expando 物件是 [名字] 屬性設定為 [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]、 [物件] > 和 IEnumerable 的列舉介面及其內容。 在就另外它也會實作 IDynamicMetaObjectProvider。 這是標準的介面,可讓物件由以配合 DLR 互通性模型撰寫的程式共用動態語言執行階段 (DLR) 內。 亦即只實作 IDynamicMetaObjectProvider 介面的物件跨.NET 動態語言共用。 expando 物件可以傳遞給,說,一個 IronRuby 元件。 您 can’t 嗎輕鬆地使用一般的.NET Managed 物件。 或者,而是,您所可以,但只要 don’t 取得動態的行為。

ExpandoObject 類別也可以實作 INotifyPropertyChanged 介面。 這可以讓類別中,以引發 PropertyChanged 事件時新增或修改成員時。 支援的 INotifyPropertyChanged 介面是在 Silverlight 和 Windows 簡報基礎應用程式前結束使用 expando 物件的索引鍵。

您建立 ExpandoObject 執行個體與其他.NET 物件一樣不同之處在於儲存執行個體變數為動態的型別:

dynamic expando = new ExpandoObject();

在此時將屬性加入至 expando 您只要指派一個新的值如下:

expando.FirstName = "Dino";

它 doesn’t 重要沒有資訊存在名成員、 它的型別或其可視性相關。 這是動態程式碼 ; 這個原因它讓極大的差異如果使用 var 關鍵字 ExpandoObject 執行個體指派給變數:

var expando = new ExpandoObject();

這個程式碼會編譯,並正常運作。但是,與此定義不允許您將任何值指派給名字屬性。ExpandoObject 類別所定義的 System.Core 有沒有這類成員。更準確的說 ExpandoObject 類別有沒有公用的成員。

這是關鍵點。當靜態型別,一個 expando 的動態時繫結作業為包括查閱成員的動態作業。當靜態型別 ExpandoObject 作業會結合成一般的編譯時間成員對應。讓編譯器知道該動態是一個特殊的類型,但並不知道 ExpandoObject 是特殊的型別。

圖 4 中, 您會看到 Visual Studio 2010 IntelliSense 選項 expando 物件宣告為動態型別時,以及當視為一般的.NET 物件。在後者的情況下 IntelliSense 顯示您預設 System.Object 成員加上集合類別的延伸方法的清單。

圖 4 Visual Studio 2010 IntelliSense] 和 [Expando 物件

它應該也要注意某些商業的工具,在某些情況下會超出這個基本行為。圖 5 顯示的擷取目前物件上定義的成員清單的 ReSharper 5.0。如果成員以程式設計方式加入,透過索引子,就 doesn’t 發生這個情況。

圖 5 的 在 Expando 物件使用的 ReSharper 5.0 IntelliSense

若要將方法加入至 expando 物件,您只是定義為一的屬性除了表達行為使用動作 <T> 或函式 <T> 委派。以下為範���:

person.GetFullName = (Func<String>)(() => { 
  return String.Format("{0}, {1}", 
    person.LastName, person.FirstName); 
});

方法 GetFullName 傳回 String,藉由組合假設為 expando 物件上使用最後一個名稱] 和 [名字屬性取得。 如果您嘗試存取遺漏的成員上 expando 物件,收到 RuntimeBinderException 例外狀況。

XML 驅動程式

若要將連接一起我顯示您到目前為止的概念,讓我將引導您完成範例位置在 XML 檔中定義的資料結構和 UI 的結構。 檔案的內容是剖析以 expando 物件的集合,並由應用程式處理。 的應用程式但是,只適用於以動態方式呈現資訊,和未繫結至任何靜態型別。

圖 3 在程式碼定義動態定義的人 expando 物件的清單。 您所預期,如果您將新的節點加入 XML 結構描述,在 expando 物件中會建立新的屬性。 如果需要從外部來源讀取成員的名稱,您應該採用索引子將其新增至 expando 的 API。 ExpandoObject 類別明確實作 IDictionary < [String]、 [物件] > 介面。 這表示您需要隔離 ExpandoObject 介面,從字典類型來使用索引子的 API 或 Add 方法:

(person as IDictionary<String, Object>)[child.Name] = child.Value;

因為這項行為的只被需要編輯 XML 檔案,若要使用不同的資料集。 但如何可以您耗用這動態地變更資料? 您的 UI 必須是彈性來接收變數的資料集。

let’s 考慮只會顯示到主控台的資料的簡單範例。 假設 XML 檔案包含說明預期的 UI 的區段,就表示您的內容中的任何項目。 範例的目的這裡 ’s 我的有:

<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 物件會包含該的輸出格式,加上其值為要顯示的成員名稱。 給定的人動態物件,您必須載入值為指定成員使用如下的程式碼:

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]、 [物件] >,您可以使用 [索引子 API 來取得和設定值。

最後,從 expando 物件擷取的值清單的實際顯示會傳遞至主控台]。圖 6 顯示兩個畫面範例主控台應用程式的唯一的差別是基礎的 XML 檔案的結構。

圖 6 的 導向的 XML 檔案的兩個範例主控台應用程式

無可否認地,這是一項簡單的範例,但是讓它正常運作所需的機制是與更有趣的範例相似。試試看,以及共用您的意見反應!

Dino Esposito 是從 Microsoft 按下的 「 程式設計 ASP.NET MVC 」 作者和 coauthor 的 「 Microsoft.NET:架構企業的應用程式 」 (Microsoft 按,2008年)。根據在義大利,Esposito 是在世界各地的業界事件頻繁的喇叭。您可以加入他的部落格,在 weblogs.asp.net/despos

多虧了要檢閱這份文件的下列的技術專家:Eric Lippert