Передайте лямбда-выражение вместо IComparer или IEqualityComparer или любого интерфейса одного метода?
Мне довелось увидеть какой-то код, в котором этот парень передал выражение лямбда в ArrayList.Sort(здесь IComparer) или IEnumerable.SequenceEqual(список IEnumerable, IEqualityComparer здесь), где ожидался IComparer или IEqualityComparer.
Я не могу быть уверен, видел ли я это, или я просто мечтаю. И я не могу найти расширение в любой из этих коллекций, которая принимает Func < > или делегат в своих сигнатурах методов.
Есть ли такой метод перегрузки/расширения? Или, если нет, можно ли так обманывать это и передать алгоритм (читать делегат), где ожидается интерфейс с одним методом?
Обновление
Всем спасибо. Это то, что я думал. Должно быть, я мечтал. Я знаю, как писать конверсию. Я просто не был уверен, видел ли я что-то подобное или просто думал, что видел его.
Еще одно обновление
Послушайте, здесь я нашел один такой экземпляр. В конце концов, я не мечтал. Посмотрите что этот парень делает здесь. Что дает?
И вот еще одно обновление:
Хорошо, я понимаю. Парень использует перегрузку Comparison<T>
. Ницца. Приятно, но вполне склонно вводить вас в заблуждение. Приятно, однако. Спасибо.
Ответы
Ответ 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;
}
}