【问题标题】:Concatenate collection of XML tags to string with LINQ使用 LINQ 将 XML 标记的集合连接到字符串
【发布时间】:2009-03-31 15:20:51
【问题描述】:

我一直坚持使用我无法控制的 Web 服务,并试图将该服务返回的 XML 解析为标准对象。

部分 XML 结构如下所示

<NO>
   <L>Some text here </L>
   <L>Some additional text here </L>
   <L>Still more text here </L>
</NO>

最后,我希望得到一个类似于 "Some text here Some additional text here Still more text here"

的字符串属性

我所拥有的初始通行证如下。我认为我在正确的轨道上,但还没有完全做到:

XElement source = \\Output from the Webservice
List<IndexEntry> result;

result = (from indexentry in source.Elements(entryLevel)
    select new IndexEntry()
    {
        EtiologyCode = indexentry.Element("IE") == null ? null : indexentry.Element("IE").Value,
        //some code to set other properties in this object
        Note = (from l in indexentry.Elements("NO").Descendants
                select l.value)  //This is where I stop
                               // and don't know where to go
    }

我知道我可以在该查询的末尾添加一个 ToList() 运算符来返回集合。是否有一种操作或技术可以让我将该集合的串联内联到单个字符串?

如果不清楚,请随时询问更多信息。

谢谢。

【问题讨论】:

    标签: c# linq linq-to-xml


    【解决方案1】:

    LINQ to XML 确实是这里的方式:

    var textArray = topElement.Elements("L")
                              .Select(x => x.Value)
                              .ToArray();
    
    var text = string.Join(" ", textArray);
    

    编辑:根据评论,您似乎只需要一种表达方式即可。这很简单,虽然有点难看:

    result = (from indexentry in source.Elements(entryLevel)
        select new IndexEntry
        {
            EtiologyCode = indexentry.Element("IE") == null 
                               ? null 
                               : indexentry.Element("IE").Value,
            //some code to set other properties in this object
            Note = string.Join(" ", indexentry.Elements("NO")
                                              .Descendants()
                                              .Select(x => x.Value)
                                              .ToArray())
        };
    

    另一种选择是将其提取到单独的扩展方法中(它必须在顶级静态类中):

    public static string ConcatenateTextNodes(
        this IEnumerable<XElement> elements)
    {
        string[] values = elements.Select(x => x.Value).ToArray();
        // You could parameterise the delimiter here if you wanted
        return string.Join(" ", values);
    }
    

    然后将您的代码更改为:

    result = (from indexentry in source.Elements(entryLevel)
        select new IndexEntry
        {
            EtiologyCode = indexentry.Element("IE") == null 
                               ? null 
                               : indexentry.Element("IE").Value,
            //some code to set other properties in this object
            Note = indexentry.Elements("NO")
                             .Descendants()
                             .ConcatenateTextNodes()
        }
    

    编辑:关于效率的说明

    其他答案建议以效率的名义使用StringBuilder。在使用它之前,我会检查这是正确的方法的证据。如果你想一想,StringBuilderToArray 会做类似的事情——他们创建一个比他们需要的更大的缓冲区,向其中添加数据,在必要时调整它的大小,最后得出一个结果。希望您不需要经常调整大小。

    StringBuilderToArray 之间的差异是正在缓冲的内容 - 在 StringBuilder 中,它是您迄今为止构建的字符串的全部内容。 ToArray 只是 引用。换句话说,调整用于ToArray 的内部缓冲区的大小可能比调整用于StringBuilder 的内部缓冲区的大小要便宜,尤其是,如果单个字符串很长。

    ToArray 中进行缓冲后,string.Join 非常高效:它可以查看所有开头的字符串,计算出确切有多少空间分配,然后连接它,而不必复制实际的字符数据。

    这是在sharp contrast to a previous answer I've given - 但不幸的是,我认为我从未编写过基准。

    我当然不希望ToArray 明显变慢,而且我认为它使这里的代码更简单 - 无需使用副作用等、聚合等。

    【讨论】:

    • 乔恩,也许我的例子不清楚。我正在使用 LINQ TO XML 查询填充一个更复杂的对象。该示例仅代表所涉及的 XML 的一部分。因此,在我的底部示例中, NOTE 表示我的对象中的一个字符串属性,如果可能的话,我想填充它...
    • ...与我现有的 LINQ 查询的其余部分内联。我说清楚了吗?我看到你在提倡并理解它,我只是不确定如何在我的情况下应用它。如果有帮助,我可以为上下文添加更多代码。
    • 乔恩。感谢您的扩展答案。真的很高兴我在开始讨论这个之前去吃午饭了。
    • 乔恩。我没有足够的代表来编辑您的答案。为了以后的清晰,请修正一个小错字。在扩展方法定义中“string values =”应该是“string[] values =”谢谢。
    • 很高兴为推动 Jon Skeet 的事业尽自己的一份力量 :)
    【解决方案2】:

    另一种选择是使用 Aggregate()

    var q = topelement.Elements("L")
                      .Select(x => x.Value)
                      .Aggregate(new StringBuilder(), 
                                 (sb, x) => return sb.Append(x).Append(" "),
                                 sb => sb.ToString().Trim());
    

    edit:Aggregate 中的第一个 lambda 是 accumulator。这是获取您所有的价值并从中创造一个价值。在这种情况下,它使用您想要的文本创建一个 StringBuilder。第二个 lambda 是 结果选择器。这使您可以将累积值转换为您想要的结果。在这种情况下,将 StringBuilder 更改为 String。

    【讨论】:

    • 哦,是的。顺便说一句,您必须在代码中将 'var q =' 替换为 'Note ='。
    • 查看我的扩展答案 - 我认为与一起使用 string.Join 和 ToArray 相比,使用 StringBuilder 可能不会提高性能。它甚至可能使情况变得更糟。这肯定会使 IMO 变得更加复杂。
    【解决方案3】:

    我自己没有这方面的经验,但我觉得LINQ to XML 可以大大简化您的代码。选择 XML 文档,然后遍历它并使用 StringBuilder 将 L 元素附加到某个字符串。

    【讨论】:

      【解决方案4】:

      我和其他人一样喜欢 LINQ,但你在这里重新发明了轮子。 XmlElement.InnerText 属性完全符合要求。

      试试这个:

      using System.Xml;
      
      class Program
      {
          static void Main(string[] args)
          {
              XmlDocument d = new XmlDocument();
              string xml =
                  @"<NO>
        <L>Some text here </L>
        <L>Some additional text here </L>
        <L>Still more text here </L>
      </NO>";
              d.LoadXml(xml);
              Console.WriteLine(d.DocumentElement.InnerText);
              Console.ReadLine();
          }
      }
      

      【讨论】:

      • 这一切都很好,但我正在使用 LINQ 解析 XML 以构建业务对象,在许多情况下,我需要从 XML 中的类似结构构建这些对象内部的集合,因此,我不想稍后进行额外处理,而是将其保留在现有的 LINQ 查询中。
      猜你喜欢
      • 1970-01-01
      • 2010-09-12
      • 2016-02-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多