【问题标题】:Building Xml with XElement dynamically through recursion通过递归使用 XElement 动态构建 Xml
【发布时间】:2012-12-15 15:51:37
【问题描述】:

我是 linq to Xml 的新手。
我有一个递归方法,它作为参数 XElement root 获取,它应该以某种方式保存 XML 数据,它将代表给定递归深度的相关子树根。

void recursiveMethod(XElement root);
更具体地说,还请查看此 XML 示例:

<start>
      <Class>
           <Worker>
                <Name> Dan </Name>
                <Phone> 123 </Phone> 
                <Class>
                      <Address>
                           <Street> yellow brick road </Street>
                           <Zip Code> 123456 </Zip Code>
                      </Address>
                </Class>
            </Worker>
      </Class>
...
</start>    

您可以想象,Name 是值类型,而 Address 是类引用。
Xml 信息应通过反射动态添加(以自上而下的方法)。

长话短说,假设我正在调查Worker Class 并到达Address Class 并且想要“向下钻取”,所以我想用正确的引用调用我的递归方法当前 Worker 类的子节点作为新的 XElement 根,因此我将能够将我通过反射在下面的 Address 类一递归深度中找到的内容添加到它。

请注意,此引用应为 XElement 类型。

我该怎么做?

编辑:如果您对做所有这些事情有其他想法,但不是使用XElement,我也很乐意听到,尽管我更喜欢使用XElement 参数。

另一个问题
我已经开始以一种天真的方式实现它,比如遍历所有字段(FieldInfo[] 的变量),如果遇到值类型(IsValueType),我正在做类似

 root.Add(new XElement("Field",
                      new XElement("Type", ...),
                      new XElement("Variable Name", ...),
                      new XElement("Value", ...)));     

所以,只是为了一般知识:
1.有没有办法只获得一个节点对其后代的引用,这样在较低的递归级别我就可以像上面那样做另一个 root.Add(...) 但这个根将是对前根的孩子? (这意味着在没有 Linq 语法的情况下进行整个操作)

2.我已经设法通过反射获得私有字段值而不使用属性,这有问题吗?我应该始终通过反射中的属性获取值吗?

【问题讨论】:

    标签: c# xml linq reflection linq-to-xml


    【解决方案1】:

    此扩展方法将以所需格式构建 XElement:

    public static class Extensions
    {
        public static XElement ToXml<T>(this T obj)
        {
            Type type = typeof(T);
    
            return new XElement("Class",
                        new XElement(type.Name,
                            from pi in type.GetProperties()
                            where !pi.GetIndexParameters().Any()
                            let value = (dynamic)pi.GetValue(obj, null)
                            select pi.PropertyType.IsPrimitive || 
                                   pi.PropertyType == typeof(string) ?
                                    new XElement(pi.Name, value) : 
                                    Extensions.ToXml(value)
                            )
                        );
        }
    }
    

    这里发生了什么:

    • 我们获取传递对象的公共属性(您可以添加 BindingFlags 来过滤属性)。
    • 接下来我验证属性是否具有索引参数并跳过这些属性。
    • 接下来我将属性值设为dynamic 对象。这很重要,否则在递归调用ToXml&lt;T&gt; 方法时,属性值类型将被推断为object
    • 我检查属性类型是原始类型(int、byte、long、single、double 等)还是字符串
    • 对于原始类型,我们编写具有属性值的元素。对于其他类型(复杂),我们递归地开始构建 XElement

    用法:

    Worker worker = new Worker()
    {
        Name = "Serge",
        Phone = "911",
        Address = new Address() { Street = "Elm street", ZipCode = 666 }
    };
    
    XElement xml = worker.ToXml();
    

    结果:

    <Class>
      <Worker>
        <Name>Serge</Name>
        <Phone>911</Phone>
        <Class>
          <Address>
            <Street>Elm street</Street>
            <ZipCode>666</ZipCode>
          </Address>
        </Class>
      </Worker>
    </Class>
    

    但是 你应该小心两个对象相互引用的情况(在这种情况下会发生无限递归)

    在这种情况下,您可以使用字典或哈希集之类的东西来存储 xml 中已经存在的所有对象:

    Type type = obj.GetType();
    if (set.Contains(obj))
        return new XElement("Class", new XAttribute("name", type.Name));
    set.Add(obj);
    return new XElement("Class", ...);
    

    【讨论】:

    • 好的,谢谢,当两个对象在递归时相互引用时,你将如何解决这样的问题?
    • @JavaSa 好吧,我认为将所有对象存储在 Dictionary 中可以帮助解决这个问题 - 在插入类元素之前,只需验证您是否已经处理过该对象
    • 太棒了!因为我想保存地图,非常感谢你!我正在尝试学习您编写的类似 SQL 的新语法(Linq)。如果我需要更多说明,我会写在这里
    • @JavaSa 实际上有一个类 HashSet&lt;T&gt; 如果您不想编写对 xml 中已经存在的元素的引用,则它更合适。我也忘了对属性值做空检查,所以添加检查:)
    • @JavaSa 抱歉,现在需要去,但我认为最好为另一个问题开始新问题
    猜你喜欢
    • 1970-01-01
    • 2013-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-11
    • 2020-10-04
    相关资源
    最近更新 更多