65.9K
CodeProject 正在变化。 阅读更多。
Home

动态 XML

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2013 年 10 月 11 日

CPOL

7分钟阅读

viewsIcon

15317

C# 4.0 中的一个新功能——一个很棒的新功能是新的关键字‘dynamic’。动态变量是在运行时绑定的,这在很多时候都非常有帮助。

C# 4.0 中的一个新功能——一个很棒的新功能是新的关键字‘dynamic’。动态变量是在运行时绑定的,这在很多时候都非常有帮助,例如,它可以简化对 COM 对象的访问。我不想更详细地解释‘dynamic’。我想展示它的有用用法。关于 dynamic 关键字的更多详细信息,请参阅此处 - http://msdn.microsoft.com/en-us/library/dd264741.aspx
最近,我必须阅读复杂的 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;
}
}

最后一个方法有点像伪构造函数——我把它写成一个方法,以避免与前面带字符串参数的构造函数冲突。

所以,几乎最后一步是处理客户端对属性的请求。首先,我注释掉了 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;
}
}


如果实际元素列表为空,我们返回一个带有 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;
}
}

此方法总是在 TryGetMember 之后调用,所以必须保留 TryGetMember 的结果来服务此方法。显然,我们保留了前一个结果在 Elements 列表中,所以现在我们检查这个列表是否不为空,以及我们是否可以从索引元素中获取数字,因为不一定是数字,我们可以键入类似这样的内容:dynamicVar.property["key"]。Indexes 是一个表,因为我们可以请求多个维度,例如 dynamicVar.property[0,1,"key"]。我假设我们只请求一个数字一维索引,所以如果我们有非空的 Elements 并且可以获取索引,我们就返回一个带有值或新实际节点的 DynamicXml。

Eval() 方法看起来是这样的

public string Eval()
{
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 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;
}
}

我希望这个类能对您有所帮助。
© . All rights reserved.