Отдельный список объектов на основе произвольного ключа в LINQ
У меня есть несколько объектов:
class Foo {
public Guid id;
public string description;
}
var list = new List<Foo>();
list.Add(new Foo() { id = Guid.Empty, description = "empty" });
list.Add(new Foo() { id = Guid.Empty, description = "empty" });
list.Add(new Foo() { id = Guid.NewGuid(), description = "notempty" });
list.Add(new Foo() { id = Guid.NewGuid(), description = "notempty2" });
Я хотел бы обработать этот список таким образом, чтобы поле id
было уникальным и выбрасывало не уникальные объекты (на основе id).
Лучшее, что я мог придумать, это:
list = list.GroupBy(i => i.id).Select(g=>g.First()).ToList();
Есть ли более хороший/лучший/быстрый способ достичь того же результата.
Ответы
Ответ 1
Очень элегантный и намеренный способ выявления - это определить новый метод расширения в IEnumerable
Итак, у вас есть:
list = list.Distinct(foo => foo.id).ToList();
И...
public static IEnumerable<T> Distinct<T,TKey>(this IEnumerable<T> list, Func<T,TKey> lookup) where TKey : struct {
return list.Distinct(new StructEqualityComparer<T, TKey>(lookup));
}
class StructEqualityComparer<T,TKey> : IEqualityComparer<T> where TKey : struct {
Func<T, TKey> lookup;
public StructEqualityComparer(Func<T, TKey> lookup) {
this.lookup = lookup;
}
public bool Equals(T x, T y) {
return lookup(x).Equals(lookup(y));
}
public int GetHashCode(T obj) {
return lookup(obj).GetHashCode();
}
}
Аналогичный класс-помощник может быть построен для сравнения объектов. (Это должно будет сделать больше нулевой обработки)
Ответ 2
Использование метода Distinct()
примерно в 4 раза быстрее, чем использование GroupBy() в моих неофициальных тестах. Для 1 миллиона Foo мой тест имеет Distinct() примерно через 0,89 секунды, чтобы создать уникальный массив из неисторического массива, где GroupBy() занимает около 3,4 секунды.
Вызов My Distinct() выглядит следующим образом:
var unique = list.Distinct(FooComparer.Instance).ToArray();
и FooComparer
выглядит,
class FooComparer : IEqualityComparer<Foo> {
public static readonly FooComparer Instance = new FooComparer();
public bool Equals(Foo x, Foo y) {
return x.id.Equals(y.id);
}
public int GetHashCode(Foo obj) {
return obj.id.GetHashCode();
}
}
и моя версия GroupBy()
выглядит,
var unique = (from l in list group l by l.id into g select g.First()).ToArray();
Ответ 3
Создайте IEqualityComparer<Foo>
, который возвращает true, если поля id совпадают, и передайте это оператору Distinct().
Ответ 4
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var list = new List<Foo>();
list.Add(new Foo() { id = Guid.Empty, description = "empty" });
list.Add(new Foo() { id = Guid.Empty, description = "empty" });
list.Add(new Foo() { id = Guid.NewGuid(), description = "notempty" });
list.Add(new Foo() { id = Guid.NewGuid(), description = "notempty2" });
var unique = from l in list
group l by new { l.id, l.description } into g
select g.Key;
foreach (var f in unique)
Console.WriteLine("ID={0} Description={1}", f.id,f.description);
Console.ReadKey();
}
}
class Foo
{
public Guid id;
public string description;
}
}
Ответ 5
Переопределить Equals (объект obj) и методы GetHashCode():
class Foo
{
public readonly Guid id;
public string description;
public override bool Equals(object obj)
{
return ((Foo)obj).id == id;
}
public override int GetHashCode()
{
return id.GetHashCode();
}
}
а затем просто вызовите Distinct():
list = list.Distinct().ToList();