【问题标题】:GroupBy on complex object (e.g. List<T>)复杂对象上的 GroupBy(例如 List<T>)
【发布时间】:2016-02-01 11:08:48
【问题描述】:

使用GroupBy()Count() &gt; 1 我试图在列表中查找我的类的重复实例。

类如下所示:

public class SampleObject
{
    public string Id;
    public IEnumerable<string> Events;
}

这就是我实例化和分组列表的方式:

public class Program
{
    private static void Main(string[] args)
    {
        var items = new List<SampleObject>()
        {
            new SampleObject() { Id = "Id", Events = new List<string>() { "ExampleEvent" } },
            new SampleObject() { Id = "Id", Events = new List<string>() { "ExampleEvent" } }
        };

        var duplicates = items.GroupBy(x => new { Token = x.Id, x.Events })
                         .Where(g => g.Count() > 1)
                         .Select(g => g.Key)
                         .ToList();
    }
}

duplicates 不包含任何项目。我怎样才能使分组工作?

【问题讨论】:

  • 默认情况下,列表不按项目的值进行比较。
  • @SergeyBerezovskiy - 这不是这里的问题。问题是缺少GetHashCodeEquals 覆盖。
  • @Enigmativity new { Token = x.Id, x.Events } 无法覆盖 SampleObject 的 Equals 和 GetHashCode。这里的问题完全是由x.Events比较引起的
  • 虽然我不会使用, 作为分隔符,但@Farhad (string.Join) 提出的解决方案是经典的解决方案,而且通常“足够好”。我会使用'\0' 作为分隔符。另一个“东西”......然后我会使用.Select(g =&gt; g.First()) 来检索重复的项目。
  • @SergeyBerezovskiy - 是的,很公平。我不得不求助于对原始对象使用自定义比较器。

标签: c# linq comparison grouping


【解决方案1】:

要使对象与许多 LINQ 运算符(例如 GroupByDistinct)一起使用,您必须实现 GetHashCodeEquals,或者您必须提供自定义比较器。

在您的情况下,将属性作为列表,您可能需要一个比较器,除非您将列表设为只读。

试试这个比较器:

public class SampleObjectComparer : IEqualityComparer<SampleObject>
{
    public bool Equals(SampleObject x, SampleObject y)
    {
        return x.Id == y.Id && x.Events.SequenceEqual(y.Events);
    }

    public int GetHashCode(SampleObject x)
    {
        return x.Id.GetHashCode() ^ x.Events.Aggregate(0, (a, y) => a ^ y.GetHashCode());
    }
}

现在这段代码可以工作了:

    var items = new List<SampleObject>()
    {
        new SampleObject() { Id = "Id", Events = new List<string>() { "ExampleEvent"} },
        new SampleObject() { Id = "Id", Events = new List<string>() { "ExampleEvent" } }
    };

    var comparer = new SampleObjectComparer();

    var duplicates = items.GroupBy(x => x, comparer)
                     .Where(g => g.Count() > 1)
                     .Select(g => g.Key)
                     .ToList();

【讨论】:

    【解决方案2】:

    List&lt;T&gt; 没有覆盖 Equals + GetHashCode,这就是为什么您的 GroupBy 无法按预期工作的原因。匿名类型的两个属性之一引用列表,当 GroupBy 必须比较两个列表时,使用 Object.RefernceEquals 仅检查两者是否相同引用,而不检查是否都包含示例元素。

    您可以提供自定义IEqualityComparer&lt;T&gt;

    public class IdEventComparer : IEqualityComparer<SampleObject>
    {
        public bool Equals(SampleObject x, SampleObject y)
        {
            if (object.ReferenceEquals(x, y)) 
                return true;
            if (x == null || y == null) 
                return false;
            if(x.Id != y.Id) 
                return false;
            if (x.Events == null && y.Events == null)
                return true;
            if (x.Events == null || y.Events == null)
                return false;
    
            return x.Events.SequenceEqual(y.Events);
        }
    
        public int GetHashCode(SampleObject obj)
        {
            if(obj == null) return 23;
            unchecked
            {
                int hash = 23;
                hash = (hash * 31) + obj.Id == null ? 31 : obj.Id.GetHashCode();
    
                if (obj.Events == null) return hash;
                foreach (string item in obj.Events)
                {
                    hash = (hash * 31) + (item == null ? 0 : item.GetHashCode());
                }
                return hash;
            }
        }
    }
    

    然后您可以在许多 LINQ 方法中使用它,例如 GroupBy:

    var duplicates = items.GroupBy(x => x, new IdEventComparer())
         .Where(g => g.Count() > 1)
         .Select(g => g.Key)
         .ToList();
    

    【讨论】:

    • 嗨蒂姆。很好的例子和解释。但是,当我尝试实现这一点时,使用这一行: var groupedOrderList = orders.OrderListD.GroupBy(u => u.Services[0].FeatureList, new FeatureAttributeComparer()).Select(grp => grp.First() ).ToList();我在 GroupBy 子句下收到一个错误标记,内容为:无法从用法中推断出类型参数。尝试明确指定类型参数。知道这个错误是什么吗?
    【解决方案3】:

    GroupBy() 将执行默认比较,导致它发现您的列表不相等。

    见以下代码:

    var eventList1 = new List<string>() { "ExampleEvent" };
    var eventList2 = new List<string>() { "ExampleEvent" };
    
    Console.WriteLine(eventList1.GetHashCode());
    Console.WriteLine(eventList2.GetHashCode());
    Console.WriteLine(eventList1.Equals(eventList2));
    

    两个“相等”的列表,对吧?但是,这将打印:

    796641852
    1064243573
    False
    

    所以他们不被认为是平等的,因此没有分组。

    您需要提供一个自定义比较器,用于比较对象的相关属性。请注意,如前所示,List&lt;T&gt;.GetHashCode() 不能正确表示列表中的项目

    您可以这样做(来自Good GetHashCode() override for List of Foo objects respecting the orderLINQ GroupBy on multiple ref-type fields; Custom EqualityComparer):

    public class SampleObjectComparer : IEqualityComparer<SampleObject>
    {
        public bool Equals(SampleObject a, SampleObject b)
        {
            return a.Id == b.Id 
                && a.Events.SequenceEqual(b.Events);
        }
    
        public int GetHashCode(SampleObject a)
        {
            int hash = 17;
    
            hash = hash * 23 + a.Id.GetHashCode();
    
            foreach (var evt in a.Events)
            {
                hash = hash * 31 + evt.GetHashCode();
            }           
    
            return hash;
        }
    }
    

    并像这样使用它:

    var eventList1 = new List<string>() { "ExampleEvent" };
    var eventList2 = new List<string>() { "ExampleEvent" };
    
    var items = new List<SampleObject>()
    {
        new SampleObject() { Id = "Id", Events = eventList1 },
        new SampleObject() { Id = "Id", Events = eventList2 }
    };
    
    var duplicates = items.GroupBy(x => x, new SampleObjectComparer())
                     .Where(g => g.Count() > 1)
                     .Select(g => g.Key)
                     .ToList();
    
    Console.WriteLine(duplicates.Count);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-05-23
      • 2019-10-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-23
      • 1970-01-01
      相关资源
      最近更新 更多