Передать лямбда-выражение вместо IComparer, IEqualityComparer или любого интерфейса с одним методом? - PullRequest
55 голосов
/ 07 июля 2010

Я случайно увидел код, в котором этот парень передавал лямбда-выражение в ArrayList.Sort (здесь IComparer) или IEnumerable.SequenceEqual (список IEnumerable, здесь IEqualityComparer), где ожидался IComparer или IEqualityComparer.

Хотя я не уверен, видел ли я это или просто сплю.И я не могу найти расширение ни в одной из этих коллекций, которое принимает Func <> или делегат в своих сигнатурах методов.

Существует ли такой метод перегрузки / расширения?Или, если нет, то можно ли так обойти и передать алгоритм (чтение делегата), где ожидается интерфейс с одним методом?

Обновление Спасибо всем.Это то, о чем я думал.Должно быть, я мечтал.Я знаю, как написать конверсию.Я просто не был уверен, видел ли я что-то подобное или просто думал, что видел это.

Еще одно обновление Послушайте, я нашел один такой экземпляр.Я не мечтал в конце концов.Посмотрите на что этот парень делает здесь .Что дает?

А вот еще одно обновление: Хорошо, я понял.Парень использует перегрузку Comparison<T>.Ницца.Приятно, но совершенно склонно вводить вас в заблуждение.Хотя хорошо.Благодаря.

Ответы [ 8 ]

23 голосов
/ 24 сентября 2013

Я также поискал в Интернете решение, но не нашел ни одного удовлетворительного.Итак, я создал универсальный EqualityComparerFactory:

using System;
using System.Collections.Generic;

/// <summary>
/// Utility class for creating <see cref="IEqualityComparer{T}"/> instances 
/// from Lambda expressions.
/// </summary>
public static class EqualityComparerFactory
{
    /// <summary>Creates the specified <see cref="IEqualityComparer{T}" />.</summary>
    /// <typeparam name="T">The type to compare.</typeparam>
    /// <param name="getHashCode">The get hash code delegate.</param>
    /// <param name="equals">The equals delegate.</param>
    /// <returns>An instance of <see cref="IEqualityComparer{T}" />.</returns>
    public static IEqualityComparer<T> Create<T>(
        Func<T, int> getHashCode,
        Func<T, T, bool> equals)
    {
        if (getHashCode == null)
        {
            throw new ArgumentNullException(nameof(getHashCode));
        }

        if (equals == null)
        {
            throw new ArgumentNullException(nameof(equals));
        }

        return new Comparer<T>(getHashCode, equals);
    }

    private class Comparer<T> : IEqualityComparer<T>
    {
        private readonly Func<T, int> _getHashCode;
        private readonly Func<T, T, bool> _equals;

        public Comparer(Func<T, int> getHashCode, Func<T, T, bool> equals)
        {
            _getHashCode = getHashCode;
            _equals = equals;
        }

        public bool Equals(T x, T y) => _equals(x, y);

        public int GetHashCode(T obj) => _getHashCode(obj);
    }
}

Идея состоит в том, что метод CreateComparer принимает два аргумента: делегат для GetHashCode (T) и делегат для Equals (T, T)

Пример:

class Person
{
    public int Id { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var list1 = new List<Person>(new[]{
            new Person { Id = 1, FirstName = "Walter", LastName = "White" },
            new Person { Id = 2, FirstName = "Jesse", LastName = "Pinkman" },
            new Person { Id = 3, FirstName = "Skyler", LastName = "White" },
            new Person { Id = 4, FirstName = "Hank", LastName = "Schrader" },
        });

        var list2 = new List<Person>(new[]{
            new Person { Id = 1, FirstName = "Walter", LastName = "White" },
            new Person { Id = 4, FirstName = "Hank", LastName = "Schrader" },
        });


        // We're comparing based on the Id property
        var comparer = EqualityComparerFactory.Create<Person>(
            a => a.Id.GetHashCode(),
            (a, b) => a.Id==b.Id);
        var intersection = list1.Intersect(list2, comparer).ToList();
    }
}
22 голосов
/ 11 июня 2012

Я не очень уверен, что это действительно полезно, так как я думаю, что для большинства случаев в Base Library, ожидающих IComparer, есть перегрузка, которая ожидает Сравнение ... но только для записи:

в .Net 4.5 они добавили метод для получения IComparer из сравнения: Comparer.Create

чтобы вы могли передать свою лямбду и получить IComparer.

11 голосов
/ 07 июля 2010

Вы можете указать лямбду для метода Array.Sort, поскольку для этого требуется метод, который принимает два объекта типа T и возвращает целое число.Таким образом, вы можете указать лямбду следующего определения (a, b) => a.CompareTo(b).Пример для сортировки целочисленного массива по убыванию:

int[] array = { 1, 8, 19, 4 };

// descending sort 
Array.Sort(array, (a, b) => -1 * a.CompareTo(b));
5 голосов
/ 07 июля 2010
public class Comparer2<T, TKey> : IComparer<T>, IEqualityComparer<T>
{
    private readonly Expression<Func<T, TKey>> _KeyExpr;
    private readonly Func<T, TKey> _CompiledFunc
    // Constructor
    public Comparer2(Expression<Func<T, TKey>> getKey)
    {
        _KeyExpr = getKey;
        _CompiledFunc = _KeyExpr.Compile();
    } 

    public int Compare(T obj1, T obj2)
    {
        return Comparer<TKey>.Default.Compare(_CompiledFunc(obj1), _CompiledFunc(obj2));
    }

    public bool Equals(T obj1, T obj2)
    { 
        return EqualityComparer<TKey>.Default.Equals(_CompiledFunc(obj1), _CompiledFunc(obj2));
    }

    public int GetHashCode(T obj)
    {
         return EqualityComparer<TKey>.Default.GetHashCode(_CompiledFunc(obj));
    }
}

используйте это так

ArrayList.Sort(new Comparer2<Product, string>(p => p.Name));
4 голосов
/ 07 июля 2010

Вы не можете передать его напрямую, однако вы можете сделать это, определив класс LambdaComparer, который исключает Func<T,T,int>, а затем использует его в CompareTo.

Это не так кратконо вы можете сделать его короче, используя некоторые творческие методы расширения на Func.

3 голосов
/ 07 июля 2010

Эти методы не имеют перегрузок, которые принимают делегат вместо интерфейса, но:

  • Обычно вы можете вернуть более простой ключ сортировки через делегата, который вы передаете Enumerable.OrderBy
  • Аналогично, вы можете позвонить Enumerable.Select перед тем, как позвонить Enumerable.SequenceEqual
  • Было бы просто написать оболочку, которая реализует IEqualityComparer<T> в терминах Func<T, T, bool>
  • F # позволяет реализовать интерфейс такого типа в терминах лямбды:)
3 голосов
/ 07 июля 2010

Я голосую за теорию сновидений.

Вы не можете передать функцию, в которой ожидается объект: производные System.Delegate (каковы лямбды) не реализуют эти интерфейсы.

То, что вы, вероятно, видели, это использование делегата Converter<TInput, TOutput>, который может моделироваться лямбда-выражением. Array.ConvertAll использует экземпляр этого делегата.

1 голос
/ 26 июля 2016

В случае, если вам нужна эта функция для использования с лямбда и, возможно, двух разных типов элементов:

static class IEnumerableExtensions
{
    public static bool SequenceEqual<T1, T2>(this IEnumerable<T1> first, IEnumerable<T2> second, Func<T1, T2, bool> comparer)
    {
        if (first == null)
            throw new NullReferenceException("first");

        if (second == null)
            throw new NullReferenceException("second");

        using (IEnumerator<T1> e1 = first.GetEnumerator())
        using (IEnumerator<T2> e2 = second.GetEnumerator())
        {
            while (e1.MoveNext())
            {
                if (!(e2.MoveNext() && comparer(e1.Current, e2.Current)))
                    return false;
            }

            if (e2.MoveNext())
                return false;
        }

        return true;
    }
}
...