使用LINQ区分字典

本文关键字:字典 LINQ 使用 | 更新日期: 2023-09-27 18:04:02

我试图使用LINQ获得不同值的字典。我试过使用这个:

var roleRefList = 
    xDocument.Root.Descendants()
             .Where(x => x.Name.LocalName.Equals("roleRef") && 
                         !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")))) && 
                         !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")))))
             .Select(l => new {
                  roleUri = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")).Value,
                  href = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")).Value
              })
             .Distinct()
             .ToDictionary(a => a.roleUri);

这里的问题是,当roleUri中有重复的条目时,就会发生错误。我正在解析XML文档,并制作xElement属性roleUriroleref的字典,如果它们存在于xElement中。

另一个解决方法是使用for循环:

Dictionary<string, string> roleRefList = new Dictionary<string, string>();
            foreach (XElement element in xDocument.Root.Descendants().Where(x => x.Name.LocalName.Equals("roleRef")))
            {
                string roelUri = Convert.ToString(element.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")));
                string href = Convert.ToString(element.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")));
                if (!string.IsNullOrEmpty(roelUri) && !string.IsNullOrEmpty(href) && !roleRefList.ContainsKey(roelUri))
                {
                    roleRefList.Add(roelUri, href);
                }
            }

但是我想用LINQ实现这个

使用LINQ区分字典

您可以编写自己的Distinct方法,将Func<T,TKey>作为参数。在LINQ

中基于任意键的不同对象列表

使用这个方法,你应该能够写:

var roleRefList = xDocument.Root.Descendants().Where(x => x.Name.LocalName.Equals("roleRef") && !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")))) && !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")))))
                           .Select(l => new
                           {
                               roleUri = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")).Value,
                               href = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")).Value
                           }).Distinct(l => l.roleUri).ToDictionary(a => a.roleUri);

或者你可以使用分组:

var roleRefList = xDocument.Root.Descendants().Where(x => x.Name.LocalName.Equals("roleRef") && !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")))) && !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")))))
                           .Select(l => new
                           {
                               roleUri = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")).Value,
                               href = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")).Value
                           })
                           .GroupBy(l => l.roleUri)
                           .ToDictionary(g => g.Key, g => g.FirstOrDefault());

XML中不允许有重复属性。如果你在roleRef元素中有两个roleURI属性,那么你将在XDocument加载时得到异常:

'roleURI'是一个重复的属性名。第42行,第42位

所以,实际上你的代码应该是这样的:
var xdoc = XDocument.Load("foo.xml");
XNamespace ns = "http://www.adventure-works.com"; // put your namespace here
Dictionary<string, string> roleRefList = 
   xdoc.Root.Descendants(ns + "roleRef")
       .Select(r => new {
             Uri = (string)r.Attribute("roleURI"),
             Href = (string)r.Attribute("href")
       })
       .Where(r => !String.IsNullOrEmpty(r.Uri) && !String.IsNullOrEmpty(r.Href))
       .ToDictionary(r => r.Uri, r => r.Href);

结果将与循环的相同。示例xml:

<root xmlns="http://www.adventure-works.com">
  <roleRef/>
  <roleRef roleURI=""/>
  <roleRef href=""/>
  <roleRef roleURI="" href=""/>
  <roleRef roleURI="a" />
  <roleRef roleURI="" href="b"/>
  <roleRef roleURI="c" href="d"/>
</root>

有两种方法可以实现这一点:

1)可以使用自定义相等比较器调用distinct方法。为了实现这一点,您需要首先编写一个包含roleUri和href字段的类。比如:

    public class AttributePair 
    {
      public string RoleUri {get; set;}
      public string Href {get; set;}
    }

下一步是为你的类编写相等比较器:

    public class AttributePairComparer : IEqualityComparer<AttributePair>
    {
        public bool Equals(AttributePair x, AttributePair y)
        {
            return x.RoleUri.Equals(y.RoleUri);
        }
        public int GetHashCode(AttributePair obj)
        {
            return obj.RoleUri.GetHashCode();
        }
    }

之后我们把相等比较器的实例传递给distinct方法:

 var roleRefList = xDocument.Root.Descendants().Where(x => x.Name.LocalName.Equals("roleRef") && !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")))) && !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")))))
                       .Select(l => new AttributePair
                       {
                           RoleUri = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")).Value,
                           Href = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")).Value
                       }).Distinct(new AttributePairComparer()).ToDictionary(a => a.RoleUri, a => a.Href);

我同意这个解决方案相当复杂,但可以说这是达到所需结果的经典方法。

2)另一个解决方案是MoreLinq的DistinctBy方法。其中可以将lambda表达式作为参数传递:

 var roleRefList = xDocument.Root.Descendants().Where(x => x.Name.LocalName.Equals("roleRef") && !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")))) && !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")))))
                       .Select(l => new
                       {
                           roleUri = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")).Value,
                           href = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")).Value
                       }).DistinctBy(p => p.roleUri).ToDictionary(a => a.roleUri, a => a.href);

3) c#的首席架构师Anders Hejlsberg提出了使用GroupBy的解决方案。