技术开发 频道

C#:用DynamicObject动态读取并操作XML

  【IT168技术】最近的一个项目用到很多不同结构的XML文件. 于是就在网上搜索了一些文章, 结合实际遇到的问题写成自己要的代码.

  目标

  基于已经有的XML文件,例如:

<root>
  
<books>
        
<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>
          
<price>26.50</price>
      
</book>
    
</books>
</root>

   或者是类似任意结构的XML文件, 可以实现初始化

dynamic dx = new DynamicXml(xml);

   并且读取属性值:

dx.books.book[0].author, dx.books.book[2].price

  解决方案

  自然是选择C# 4.0 中引入的 DynamicObject, 关键是已有的.net框架中不能提供这样的功能.那么就从这个类中继承生成子类, 同时也需要实现 IEnumerable.

  稍微介绍一下:DynamicObject 类使您能够定义可以动态对象上执行哪些操作以及如何执行这些操作。  如果只需要设置和获取属性的操作,您可以只覆盖 TrySetMember 和 TryGetMember 方法。IEnumerable 公开枚举器,该枚举器支持在非泛型集合上进行简单迭代。

  应该大家都知道要实现自定义集合的IEnumerable 就要完成

public IEnumerator GetEnumerator()

   也就是说, 为了实现需求, 至少我们需要完成三个函数:

TrySetMember, TryGetMember, GetEnumerator

  实现

  通过上面的分析, 我们知道这个类看起来应该是这样:

public class DynamicXml : DynamicObject, IEnumerable
{
public DynamicXml(string text)
{
}
  
public override bool TryGetMember(GetMemberBinder binder, out object result)
  {
}
  
public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
  {
  }

  
public IEnumerator GetEnumerator()
  {
  }
}

   在构造函数里我们希望可以读取XML的内容, 加载所有元素的值.

  在TrySetMember中, 我们为设置成员值的操作提供实现, 以便为设置属性值指定动态行为。在本文中, 我们只考虑读取XML内容, 所以不需要重写这个函数.

  在TryGetMember中, 我们要为获取成员值的操作提供实现。

  在 TryGetIndex中, 我们要为按索引获取值的操作提供实现.

        实现的代码如下:

public class DynamicXml : DynamicObject, IEnumerable{
    
private readonly List<XElement> _elements;
    
public DynamicXml(string text)
    {
        var doc
= XDocument.Parse(text);
        _elements
= new List<XElement> { doc.Root };
    }
    
protected DynamicXml(XElement element)
    {
        _elements
= new List<XElement> { element };
    }
    
protected DynamicXml(IEnumerable<XElement> elements)
    {
        _elements
= new List<XElement>(elements);
    }
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        result
= null;
        
if (binder.Name == "Value"
           result = _elements[0].Value;
        
else if (binder.Name == "Count")
            result
= _elements.Count;
        
else
        {
            var attr
= _elements[0].Attribute(
                XName.Get(binder.Name));
            
if (attr != null)
                result
= attr;
            
else
            {
                var items
= _elements.Descendants(
                    XName.Get(binder.Name));
                
if (items == null ||items.Count() == 0)                    return false;
                result
= new DynamicXml(items);
            }
        }
        
return true;
    }
    
public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
    {
        
int ndx = (int)indexes[0];        result = new DynamicXml(_elements[ndx]);
        
return true;
    }
    
public IEnumerator GetEnumerator()
    {
        foreach (var element
in _elements)
            yield
return new DynamicXml(element);
    }
}

  运用

  在具体运用中, 有些地方是非常有趣的, 因为我们有了dynamic, 所以你可以用同一个变量去获取不同结构的XML文件. 运用代码如下:

static void Main(string[] args)
{
    
string xml = @"<root>
                     <books>
                        <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>
                          <price>26.50</price>
                      </book>
                    </books>
                </root>
";    dynamic dx = new DynamicXml(xml);
    Console.WriteLine(
"----- Book List -----");
    
foreach (dynamic b in dx.books.book)
    {
        Console.WriteLine(
"author='{0}'", b.author.Value);
        print(b);
    }
    Console.WriteLine(
"------ Book List End ------");
    
string xml2 = @"<root>
                        <products>
                            <product>
                              <title>iPhone 4</title>
                              <price>2222.50</price>
                              <quantity>10</quantity>
                            </product>
                            <product>
                              <title>Lenovo IdeaPad</title>
                              <price>5432.50</price>
                              <quantity>15</quantity>
                           </product>
                        </products>
                    </root>
";
    dx
= new DynamicXml(xml2);
    Console.WriteLine(
"----- Product List -----");
    
foreach (dynamic b in dx.products.product)
    {
        Console.WriteLine(
"quantity='{0}'", b.quantity.Value);
        print(b);
    }
    Console.WriteLine(
"------ Product List End ------");
    Console.Read();
}

   我把print函数专门拿出来, 故弄玄虚一下, 因为两个XML的结构有类似的地方, 所以可以同时使用下面的函数. -- 当然对于这样的情况,我们也可以为此定义一个interface.

static void print(dynamic b)
 
 
    Console.WriteLine("Title='{0}'", b.title.Value);
    Console.WriteLine("price='{0}'", b.price.Value);
 }

   不是什么很深奥的东西, 但是希望可以为有同样问题的朋友们节省一些时间.

  如果可以较好的掌握Dynamic, 可以节省一些反射方面的代码

0
相关文章