Передайте лямбда-выражение вместо IComparer или IEqualityComparer или любого интерфейса одного метода? - программирование
Подтвердить что ты не робот

Передайте лямбда-выражение вместо IComparer или IEqualityComparer или любого интерфейса одного метода?

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

Я также поискал в Интернете решение, но не нашел ни одного удовлетворительного. Итак, я создал универсальный 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();
    }
}

Ответ 3

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

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

// descending sort 
Array.Sort(array, (a, b) => -1 * a.CompareTo(b));

Ответ 4

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));

Ответ 5

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

Это не совсем лаконично, но вы можете сократить его с помощью некоторых методов расширения объявлений на Func.

Ответ 6

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

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

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

Ответ 7

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

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

Ответ 8

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

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;
    }
}