C# 4.0 中的一个新功能——一个很棒的新功能是新的关键字‘dynamic’。动态变量是在运行时绑定的,这在很多时候都非常有帮助,例如,它可以简化对 COM 对象的访问。我不想更详细地解释‘dynamic’。我想展示它的有用用法。关于 dynamic 关键字的更多详细信息,请参阅此处 - http://msdn.microsoft.com/en-us/library/dd264741.aspx。
public IList Elements { get; set; }
最近,我必须阅读复杂的 XML,我想也许我可以使用 dynamic,而无需通过 LINQ 或其他技术来读取 XML。
以这个 XML 为例
<root>
<shop>
<book>
<author>John Savacki</author>
<title>E.G.Title</title>
<price>20.50</price>
</book>
<book>
<author>Tom Curly</author>
<title>E.G.Title 2</title>
</book>
</shop>
</root>
我想像这样读取它
dynamic xml = ......
string author = xml.shop.book[0].author
难道不优雅吗?
是的——但遗憾的是,这样的库不存在。
因此,我查找了现有的解决方案,但没有一个对我来说足够,所以我决定编写自己的 DynamicXML 类,并想与您分享我的解决方案,顺便展示新‘dynamic’特性的用法。
因为我将使用这个基类——DynamicObject。它是我们可以绑定到动态变量的新类型的基类,感谢它,我们可以捕获对我们变量的每次引用,如属性、方法等。
所以这是我的类的头部
public class DynamicXml : DynamicObject, IEnumerable
{
{
}
我利用 IEnumerable 接口来提供遍历集合的可能性,但这将在后面再说。
为了读取 XML,我只重写了基类的两个方法
TryGetMember 和 TryGetIndex。
正如你所料,第一个方法在客户端编写类似于 dynamicXML.Something 的代码时被调用,第二个方法在客户端编写类似于 dynamicXML.Something[i] 的代码时被调用。
简单的想法是:当客户端请求属性(节点)时,我们查找实际节点是否包含它。如果是,我们就检查这个节点是否包含子节点。如果包含子节点,那么我们应该返回一个新的 DynamicXML,并更改当前节点;否则,我们应该返回一个带有值的字符串。当实际节点不包含要查找的属性时,我们可以返回 null——但这里会出现一个问题。可选字段怎么办?看看我们的示例 XML——当客户端键入 xml.shop.book[1].price 时,我们应该返回 null——当然,这很好,但如果 price 也可能包含可选字段呢?例如‘discount’。
现在,当客户端键入 xml.shop.book[1].price.discount 时,他会收到 NullPointerException。我们必须避免这种情况,但我们在 TryGetMember 之后检查实际属性时没有能力检查客户端输入了什么,所以我决定始终返回一个新的 DynamicXml 对象,并提供一个像 eval() 这样的附加方法来返回最终结果。
因此,要从第二本书中获取作者,我们必须键入 xml.shop.book[1].author.eval()。
下一个选项是我们检索多个元素时,例如,我们键入 xml.shop.book.eval()——这应该返回一个包含 book 元素列表的 DynamicXml。为了满足所有这些选项,我使用 XElement 列表和一个实际值的字符串变量。
现在我们的类看起来是这样的
public class DynamicXml : DynamicObject, IEnumerable
{
{
private string value = null;
public IList Elements { get; set; }
}
现在我们需要一些构造函数——公共的和私有的。公共的供客户端从一些源(如文件名或流)创建我们的 DynamicXml。私有的将用于在响应属性请求时返回新的 DynamicXml(这是一个简单的递归)。
public class DynamicXml : DynamicObject, IEnumerable
{
private string value = null;
public IList Elements { get; set; }
public DynamicXml(Stream input) { Elements = new List { XDocument.Load(input).Root }; }
public DynamicXml(TextReader input) { Elements = new
List { XDocument.Load(input).Root }; }
public DynamicXml(string input) { Elements = new
List { XDocument.Load(input).Root }; }
private DynamicXml() { Elements = null; }
private DynamicXml(XElement input) { Elements = new
List { input }; }
private DynamicXml(IEnumerable<XElement> input) { Elements =
new List(input); }
private DynamicXml CreateValueDynamicXml(string value)
{
DynamicXml result = new DynamicXml();
result.value = value;
return result;
}
{
private string value = null;
public IList Elements { get; set; }
public DynamicXml(Stream input) { Elements = new List { XDocument.Load(input).Root }; }
public DynamicXml(TextReader input) { Elements = new
List { XDocument.Load(input).Root }; }
public DynamicXml(string input) { Elements = new
List { XDocument.Load(input).Root }; }
private DynamicXml() { Elements = null; }
private DynamicXml(XElement input) { Elements = new
List { input }; }
private DynamicXml(IEnumerable<XElement> input) { Elements =
new List(input); }
private DynamicXml CreateValueDynamicXml(string value)
{
DynamicXml result = new DynamicXml();
result.value = value;
return result;
}
}
最后一个方法有点像伪构造函数——我把它写成一个方法,以避免与前面带字符串参数的构造函数冲突。
所以,几乎最后一步是处理客户端对属性的请求。首先,我注释掉了 TryGetValue
public override bool TryGetMember(GetMemberBinder binder, out
object result)
{
if (Elements != null)
{
var elements = Elements.Descendants().Where(q =>
q.Name == binder.Name);
if (elements.Count() == 1)
{
if (elements.First().HasElements)
{
result = new DynamicXml(elements.First());
}
else
{
result = CreateValueDynamicXml(elements.First().Value);
}
}
else if (elements.Count() > 1)
{
result = new DynamicXml(elements);
}
else
{
result = CreateValueDynamicXml(null);
return false;
}
return true;
}
else
{
result = CreateValueDynamicXml(null);;
return false;
}
}
object result)
{
if (Elements != null)
{
var elements = Elements.Descendants().Where(q =>
q.Name == binder.Name);
if (elements.Count() == 1)
{
if (elements.First().HasElements)
{
result = new DynamicXml(elements.First());
}
else
{
result = CreateValueDynamicXml(elements.First().Value);
}
}
else if (elements.Count() > 1)
{
result = new DynamicXml(elements);
}
else
{
result = CreateValueDynamicXml(null);
return false;
}
return true;
}
else
{
result = CreateValueDynamicXml(null);;
return false;
}
}
如果实际元素列表为空,我们返回一个带有 null 值的下一个 DynamicXml 对象——正如我之前提到的,这是我们对可选节点的保护。如果列表有元素,那么我们就查找该节点列表中所有与请求的属性名称(通过 GetMemberBinder 对象传递)匹配的元素。如果我们找到一个元素,我们就检查它是否有子元素;如果有,我们就返回一个新的 DynamicXml,其 Elements 列表包含找到的那个元素(之后我们可以从中检索子元素)。如果找到的元素不包含任何子元素,我们就知道这是一个“叶子”节点,因此我们返回一个新的 DynamicXml,其 Elements 列表为空,但带有正确的值。如果我们找到多个匹配的元素,我们就返回一个新的 DynamicXml,其 Elements 列表填充了所有之前找到的元素。如果我们没有找到任何匹配的元素,情况与 Elements 属性为 null 的情况相同。
public override bool TryGetIndex(GetIndexBinder binder,
object[] indexes, out object result)
{
int index = 0;
if (int.TryParse(indexes[0].ToString(), out index) && Elements != null)
{
if (Elements[index].HasElements)
{
result = new DynamicXml(Elements[index]);
}
else
{
result = CreateValueDynamicXml(Elements[index].Value);
}
return true;
}
else
{
result = CreateValueDynamicXml(null);
return false;
}
}
object[] indexes, out object result)
{
int index = 0;
if (int.TryParse(indexes[0].ToString(), out index) && Elements != null)
{
if (Elements[index].HasElements)
{
result = new DynamicXml(Elements[index]);
}
else
{
result = CreateValueDynamicXml(Elements[index].Value);
}
return true;
}
else
{
result = CreateValueDynamicXml(null);
return false;
}
}
此方法总是在 TryGetMember 之后调用,所以必须保留 TryGetMember 的结果来服务此方法。显然,我们保留了前一个结果在 Elements 列表中,所以现在我们检查这个列表是否不为空,以及我们是否可以从索引元素中获取数字,因为不一定是数字,我们可以键入类似这样的内容:dynamicVar.property["key"]。Indexes 是一个表,因为我们可以请求多个维度,例如 dynamicVar.property[0,1,"key"]。我假设我们只请求一个数字一维索引,所以如果我们有非空的 Elements 并且可以获取索引,我们就返回一个带有值或新实际节点的 DynamicXml。
Eval() 方法看起来是这样的
public string Eval()
{
return string.IsNullOrEmpty(value) ? null : value;
}
{
return string.IsNullOrEmpty(value) ? null : value;
}
我认为这非常简单,不需要任何注释。
我处理的最后一个方法是 GetEnumerator(),作为 IEnumerable 接口的实现
public IEnumerator GetEnumerator()
{
foreach (var element in Elements)
{
if (element.HasElements)
{
yield return new DynamicXml(element);
}
else
{
yield return CreateValueDynamicXml(element.Value);
}
}
}
{
foreach (var element in Elements)
{
if (element.HasElements)
{
yield return new DynamicXml(element);
}
else
{
yield return CreateValueDynamicXml(element.Value);
}
}
}
我写这个方法是为了处理像这样的请求
foreach (var book in xml.shop.book.eval())
{
Console.WriteLine(book.author.eval());
}
我认为上面的方法也很容易理解。可能存在的例外是 **yield** 关键字。要理解这一点,我建议阅读这篇文章
下面我展示了完整的、可直接使用的 DynamicXml 类代码
public class DynamicXml : DynamicObject, IEnumerable
{
private string value = null;
public IList Elements { get; set; }
public DynamicXml(Stream input) { Elements = new
List { XDocument.Load(input).Root }; }
public DynamicXml(TextReader input) { Elements = new
List { XDocument.Load(input).Root }; }
public DynamicXml(string input) { Elements = new
List { XDocument.Load(input).Root }; }
private DynamicXml() { Elements = null; }
private DynamicXml(XElement input) { Elements = new
List { input }; }
private DynamicXml(IEnumerable<XElement> input) { Elements =
new List(input); }
private DynamicXml CreateValueDynamicXml(string value)
{
DynamicXml result = new DynamicXml();
result.value = value;
return result;
}
public override bool TryGetMember(GetMemberBinder binder, out
object result)
{
if (Elements != null)
{
var elements = Elements.Descendants().Where(q =>
q.Name == binder.Name);
if (elements.Count() == 1)
{
if (elements.First().HasElements)
{
result = new DynamicXml(elements.First());
}
else
{
result = CreateValueDynamicXml(elements.First().Value);
}
}
else if (elements.Count() > 1)
{
result = new DynamicXml(elements);
}
else
{
result = CreateValueDynamicXml(null);
return false;
}
return true;
}
else
{
result = CreateValueDynamicXml(null);
return false;
}
}
public override bool TryGetIndex(GetIndexBinder binder,
object[] indexes, out object result)
{
int index = 0;
if (int.TryParse(indexes[0].ToString(), out index) &&
Elements != null)
{
if (Elements[index].HasElements)
{
result = new DynamicXml(Elements[index]);
}
else
{
result = CreateValueDynamicXml(Elements[index].Value);
}
return true;
}
else
{
result = CreateValueDynamicXml(null);
return false;
}
}
public IEnumerator GetEnumerator()
{
foreach (var element in Elements)
{
if (element.HasElements)
{
yield return new DynamicXml(element);
}
else
{
yield return CreateValueDynamicXml(element.Value);
}
}
}
public string Eval()
{
return string.IsNullOrEmpty(value) ? null : value;
}
}
{
private string value = null;
public IList Elements { get; set; }
public DynamicXml(Stream input) { Elements = new
List { XDocument.Load(input).Root }; }
public DynamicXml(TextReader input) { Elements = new
List { XDocument.Load(input).Root }; }
public DynamicXml(string input) { Elements = new
List { XDocument.Load(input).Root }; }
private DynamicXml() { Elements = null; }
private DynamicXml(XElement input) { Elements = new
List { input }; }
private DynamicXml(IEnumerable<XElement> input) { Elements =
new List(input); }
private DynamicXml CreateValueDynamicXml(string value)
{
DynamicXml result = new DynamicXml();
result.value = value;
return result;
}
public override bool TryGetMember(GetMemberBinder binder, out
object result)
{
if (Elements != null)
{
var elements = Elements.Descendants().Where(q =>
q.Name == binder.Name);
if (elements.Count() == 1)
{
if (elements.First().HasElements)
{
result = new DynamicXml(elements.First());
}
else
{
result = CreateValueDynamicXml(elements.First().Value);
}
}
else if (elements.Count() > 1)
{
result = new DynamicXml(elements);
}
else
{
result = CreateValueDynamicXml(null);
return false;
}
return true;
}
else
{
result = CreateValueDynamicXml(null);
return false;
}
}
public override bool TryGetIndex(GetIndexBinder binder,
object[] indexes, out object result)
{
int index = 0;
if (int.TryParse(indexes[0].ToString(), out index) &&
Elements != null)
{
if (Elements[index].HasElements)
{
result = new DynamicXml(Elements[index]);
}
else
{
result = CreateValueDynamicXml(Elements[index].Value);
}
return true;
}
else
{
result = CreateValueDynamicXml(null);
return false;
}
}
public IEnumerator GetEnumerator()
{
foreach (var element in Elements)
{
if (element.HasElements)
{
yield return new DynamicXml(element);
}
else
{
yield return CreateValueDynamicXml(element.Value);
}
}
}
public string Eval()
{
return string.IsNullOrEmpty(value) ? null : value;
}
}
我希望这个类能对您有所帮助。