C# 4.0 引入 dynamic 關鍵字,配合 DynamicObject 可構建極具表達力的 XML 操作 DSL。本文基於 Alexandra Rusina 的 DynamicXMLNode 實作,深度解析動態成員存取機制與陷阱。
public class DynamicXMLNode : DynamicObject {
private readonly XElement _node;
public DynamicXMLNode(string name) : this(new XElement(name)) { }
public DynamicXMLNode(XElement node) { _node = node; }
// 寫入屬性/子元素:<Name>Value</Name> 或 @Attr="Value"
public override bool TrySetMember(SetMemberBinder binder, object value) {
var name = binder.Name;
if (value is DynamicXMLNode childNode) {
_node.Add(childNode._node); // 子元素
} else {
_node.SetAttributeValue(name, value); // 屬性
}
return true;
}
// 讀取子元素:若不存在返回 null → 觸發 RuntimeBinderException
public override bool TryGetMember(GetMemberBinder binder, out object result) {
var child = _node.Element(binder.Name);
if (child != null) {
result = new DynamicXMLNode(child);
return true;
}
result = null;
return false; // 關鍵:返回 false 會拋出 RuntimeBinderException
}
// 隱式轉換為 XElement 便於互操作
public static implicit operator XElement(DynamicXMLNode node) => node._node;
}
dynamic contact = new DynamicXMLNode("Contacts");
contact.Name = "Patrick Hines"; // 設定屬性 @Name
// 正確:先建立子節點物件再賦值
contact.Address = new DynamicXMLNode("Address");
contact.Address.Street = "123 Main St"; // <Address><Street>123 Main St</Street></Address>
// 錯誤:直接對不存在的子節點賦值屬性
// contact.Address.Street = "123 Main St"; // RuntimeBinderException: 'Address' 不存在

| 操作 | 觸發方法 | 行為 |
|---|---|---|
contact.Name = "X" | TrySetMember | 設定屬性 @Name="X" |
contact.Address = new DynamicXMLNode() | TrySetMember | 新增子元素 <Address></Address> |
contact.Address.Street (讀取) | TryGetMember | 查找子元素 Street,存在返回包裝器,否則 null + false → 拋異常 |
contact.Address.Street = "Y" (寫入) | TryGetMember → TrySetMember | 先獲取 Address 包裝器,再對其 TrySetMember("Street", "Y") |
// 索引器:contact.Children[0].Name
public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) {
int idx = (int)indexes[0];
var child = _node.Elements().ElementAtOrDefault(idx);
result = child != null ? new DynamicXMLNode(child) : null;
return child != null;
}
// 動態方法:contact.AddChild("Phone", "123")
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) {
if (binder.Name == "AddChild" && args.Length == 2) {
_node.Add(new XElement(args[0].ToString(), args[1]));
result = null; return true;
}
return base.TryInvokeMember(binder, args, out result);
}
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
DynamicXMLNode | 語法極簡、動態構建、無需定義類別 | 無編譯期檢查、效能稍低、命名空間處理繁瑣 | 測試資料構建、動態配置、原型開發 |
XDocument/LINQ to XML | 強型別、標準 API、效能高 | 語法冗長 | 生產環境 XML 處理 |
XmlSerializer | POCO 雙向映射、Schema 驗證 | 需預定義類別、嚴格對應 | 業務實體持久化、API 契約 |