Подтвердить что ты не робот

Можете ли вы создать простой "EqualityComparer <T>", используя выражение лямбда

ВАЖНО: ВОПРОС: НЕ ЛИЦО-SQL. Это LINQ для объектов.

Короткий вопрос:

Есть ли простой способ в LINQ для объектов получить отдельный список объектов из списка на основе свойства ключа для объектов.

Длинный вопрос:

Я пытаюсь сделать операцию Distinct() в списке объектов, у которых есть ключ как один из их свойств.

class GalleryImage {
   public int Key { get;set; }
   public string Caption { get;set; }
   public string Filename { get; set; }
   public string[] Tags {g et; set; }
}

У меня есть список объектов Gallery, содержащих GalleryImage[].

Из-за того, как работает веб-сервис [sic], у меня есть дубликаты GalleryImage объект. я подумал, что было бы просто использовать Distinct() для получения отдельного списка.

Это запрос LINQ, который я хочу использовать:

var allImages = Galleries.SelectMany(x => x.Images);
var distinctImages = allImages.Distinct<GalleryImage>(new 
                     EqualityComparer<GalleryImage>((a, b) => a.id == b.id));

Проблема заключается в том, что EqualityComparer является абстрактным классом.

Я не хочу:

  • реализовать IEquatable на GalleryImage, потому что он сгенерирован
  • нужно написать отдельный класс для реализации IEqualityComparer как показанный здесь

Есть ли конкретная реализация EqualityComparer где-то, что мне не хватает?

Я бы подумал, что будет простой способ получить "разные" объекты из набора, основанного на ключе.

4b9b3361

Ответ 1

(Здесь есть два решения - см. конец для второго):

My MiscUtil библиотека имеет класс ProjectionEqualityComparer (и два поддерживающих класса, чтобы использовать вывод типа).

Вот пример его использования:

EqualityComparer<GalleryImage> comparer = 
    ProjectionEqualityComparer<GalleryImage>.Create(x => x.id);

Здесь код (комментарии удалены)

// Helper class for construction
public static class ProjectionEqualityComparer
{
    public static ProjectionEqualityComparer<TSource, TKey>
        Create<TSource, TKey>(Func<TSource, TKey> projection)
    {
        return new ProjectionEqualityComparer<TSource, TKey>(projection);
    }

    public static ProjectionEqualityComparer<TSource, TKey>
        Create<TSource, TKey> (TSource ignored,
                               Func<TSource, TKey> projection)
    {
        return new ProjectionEqualityComparer<TSource, TKey>(projection);
    }
}

public static class ProjectionEqualityComparer<TSource>
{
    public static ProjectionEqualityComparer<TSource, TKey>
        Create<TKey>(Func<TSource, TKey> projection)
    {
        return new ProjectionEqualityComparer<TSource, TKey>(projection);
    }
}

public class ProjectionEqualityComparer<TSource, TKey>
    : IEqualityComparer<TSource>
{
    readonly Func<TSource, TKey> projection;
    readonly IEqualityComparer<TKey> comparer;

    public ProjectionEqualityComparer(Func<TSource, TKey> projection)
        : this(projection, null)
    {
    }

    public ProjectionEqualityComparer(
        Func<TSource, TKey> projection,
        IEqualityComparer<TKey> comparer)
    {
        projection.ThrowIfNull("projection");
        this.comparer = comparer ?? EqualityComparer<TKey>.Default;
        this.projection = projection;
    }

    public bool Equals(TSource x, TSource y)
    {
        if (x == null && y == null)
        {
            return true;
        }
        if (x == null || y == null)
        {
            return false;
        }
        return comparer.Equals(projection(x), projection(y));
    }

    public int GetHashCode(TSource obj)
    {
        if (obj == null)
        {
            throw new ArgumentNullException("obj");
        }
        return comparer.GetHashCode(projection(obj));
    }
}

Второе решение

Чтобы сделать это только для Distinct, вы можете использовать расширение DistinctBy в MoreLINQ:

    public static IEnumerable<TSource> DistinctBy<TSource, TKey>
        (this IEnumerable<TSource> source,
         Func<TSource, TKey> keySelector)
    {
        return source.DistinctBy(keySelector, null);
    }

    public static IEnumerable<TSource> DistinctBy<TSource, TKey>
        (this IEnumerable<TSource> source,
         Func<TSource, TKey> keySelector,
         IEqualityComparer<TKey> comparer)
    {
        source.ThrowIfNull("source");
        keySelector.ThrowIfNull("keySelector");
        return DistinctByImpl(source, keySelector, comparer);
    }

    private static IEnumerable<TSource> DistinctByImpl<TSource, TKey>
        (IEnumerable<TSource> source,
         Func<TSource, TKey> keySelector,
         IEqualityComparer<TKey> comparer)
    {
        HashSet<TKey> knownKeys = new HashSet<TKey>(comparer);
        foreach (TSource element in source)
        {
            if (knownKeys.Add(keySelector(element)))
            {
                yield return element;
            }
        }
    }

В обоих случаях ThrowIfNull выглядит следующим образом:

public static void ThrowIfNull<T>(this T data, string name) where T : class
{
    if (data == null)
    {
        throw new ArgumentNullException(name);
    }
}

Ответ 2

Основываясь на ответе Чарли Флауэр, вы можете создать свой собственный метод расширения, чтобы делать то, что хотите, которое внутри себя использует группировку:

    public static IEnumerable<T> Distinct<T, U>(
        this IEnumerable<T> seq, Func<T, U> getKey)
    {
        return
            from item in seq
            group item by getKey(item) into gp
            select gp.First();
    }

Вы также можете создать общий класс, полученный из EqualityComparer, но похоже, что вы хотели бы избежать этого:

    public class KeyEqualityComparer<T,U> : IEqualityComparer<T>
    {
        private Func<T,U> GetKey { get; set; }

        public KeyEqualityComparer(Func<T,U> getKey) {
            GetKey = getKey;
        }

        public bool Equals(T x, T y)
        {
            return GetKey(x).Equals(GetKey(y));
        }

        public int GetHashCode(T obj)
        {
            return GetKey(obj).GetHashCode();
        }
    }

Ответ 3

Это лучшее, что я могу придумать для проблемы. Все еще любопытно, есть ли хороший способ создать EqualityComparer на лету, хотя.

Galleries.SelectMany(x => x.Images).ToLookup(x => x.id).Select(x => x.First());

Создайте таблицу поиска и возьмите "верх" от каждого

Примечание: это то же самое, что и @charlie, но с использованием ILookup, который, я думаю, является тем, что группа должна быть в любом случае.

Ответ 4

Вы можете группировать по значению ключа, а затем выбирать верхний элемент из каждой группы. Будет ли это работать для вас?

Ответ 5

Как насчет выброса IEqualityComparer generic class?

public class ThrowAwayEqualityComparer<T> : IEqualityComparer<T>
{
  Func<T, T, bool> comparer;

  public ThrowAwayEqualityComparer<T>(Func<T, T, bool> comparer)
  {
    this.comparer = comparer;
  }

  public bool Equals(T a, T b)
  {
    return comparer(a, b);
  }

  public int GetHashCode(T a)
  {
    return a.GetHashCode();
  }
}

Итак, теперь вы можете использовать Distinct.

var distinctImages = allImages.Distinct(
   new ThrowAwayEqualityComparer<GalleryImage>((a, b) => a.Key == b.Key));

Вы можете уйти с <GalleryImage>, но я не уверен, может ли компилятор указать тип (сейчас у него нет доступа.)

И в дополнительном методе расширения:

public static class IEnumerableExtensions
{
  public static IEnumerable<TValue> Distinct<TValue>(this IEnumerable<TValue> @this, Func<TValue, TValue, bool> comparer)
  {
    return @this.Distinct(new ThrowAwayEqualityComparer<TValue>(comparer);
  }

  private class ThrowAwayEqualityComparer...
}

Ответ 6

Вот интересная статья, которая расширяет LINQ для этой цели... http://www.singingeels.com/Articles/Extending_LINQ__Specifying_a_Property_in_the_Distinct_Function.aspx

По умолчанию Distinct сравнивает объекты на основе их hashcode - чтобы ваши объекты работали с Distinct, вы могли бы переопределить метод GetHashcode.. но вы упомянули, что вы извлекаете свои объекты из веб-службы, поэтому вы, возможно, не сможете для этого в этом случае.

Ответ 7

реализовать IEquatable в GalleryImage, поскольку он создан

Другой подход состоял бы в том, чтобы генерировать GalleryImage как частичный класс, а затем иметь другой файл с реализацией наследования и IEquatable, Equals, GetHash.