复杂对象的 GroupBy(例如 List<T>)
GroupBy on complex object (e.g. List<T>)
使用 GroupBy()
和 Count() > 1
我试图在列表中找到我的 class 的重复实例。
class 看起来像这样:
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
不包含任何项目。如何进行分组?
要使对象与许多 LINQ 运算符一起工作,例如 GroupBy
或 Distinct
,您必须实现 GetHashCode
和 Equals
,或者您必须提供自定义比较器。
在您的情况下,使用 属性 作为列表,您可能需要一个比较器,除非您将列表设置为只读。
试试这个比较器:
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();
List<T>
没有覆盖 Equals
+ GetHashCode
,这就是为什么您的 GroupBy
没有按预期工作。匿名类型的两个属性之一引用列表,当 GroupBy
必须比较两个列表时 Object.RefernceEquals
被使用,它只检查两者是否是相同的引用而不检查是否都包含样本元素.
您可以提供自定义 IEqualityComparer<T>
:
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();
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));
两个 "equal" 列表,对吧?但是,这将打印:
796641852
1064243573
False
所以他们不被认为是平等的,因此没有分组。
您需要提供一个自定义比较器,它将比较对象的相关属性。请注意,如前所示,List<T>.GetHashCode()
未正确表示 列表中的项目 。
你可以这样做(来自Good GetHashCode() override for List of Foo objects respecting the order and LINQ 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);
使用 GroupBy()
和 Count() > 1
我试图在列表中找到我的 class 的重复实例。
class 看起来像这样:
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
不包含任何项目。如何进行分组?
要使对象与许多 LINQ 运算符一起工作,例如 GroupBy
或 Distinct
,您必须实现 GetHashCode
和 Equals
,或者您必须提供自定义比较器。
在您的情况下,使用 属性 作为列表,您可能需要一个比较器,除非您将列表设置为只读。
试试这个比较器:
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();
List<T>
没有覆盖 Equals
+ GetHashCode
,这就是为什么您的 GroupBy
没有按预期工作。匿名类型的两个属性之一引用列表,当 GroupBy
必须比较两个列表时 Object.RefernceEquals
被使用,它只检查两者是否是相同的引用而不检查是否都包含样本元素.
您可以提供自定义 IEqualityComparer<T>
:
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();
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));
两个 "equal" 列表,对吧?但是,这将打印:
796641852
1064243573
False
所以他们不被认为是平等的,因此没有分组。
您需要提供一个自定义比较器,它将比较对象的相关属性。请注意,如前所示,List<T>.GetHashCode()
未正确表示 列表中的项目 。
你可以这样做(来自Good GetHashCode() override for List of Foo objects respecting the order and LINQ 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);