传递一个 lambda 表达式代替 IComparer 或 IEqualityComparer 或任何单方法接口?

我碰巧看到一些代码,其中这个家伙将一个 lambda 表达式传递给一个数组列表。排序(这里是 IComparer)或 IEnumable。其中需要一个 IComparer 或 IEqualityComparer。

我不确定我是否看到了,或者我只是在做梦。我似乎找不到这些集合的任何扩展,它们在方法签名中接受 Func < > 或委托。

有这样的重载/扩展方法吗?或者,如果没有,是否有可能像这样胡闹,并传递一个算法(读委托) ,其中需要一个单方法接口?

Update 谢谢大家。我也是这么想的。我一定是在做梦。我知道怎么写转换。我只是不确定我是否见过那样的东西或者只是认为我见过它。

又有新消息了 看,在这里,我发现了一个这样的例子。我毕竟不是在做梦。看看 这家伙在这里干什么。什么给?

下面是另一个更新: 好吧,我明白了。这家伙在使用 Comparison<T>过载。不错嘛。很好,但很容易误导你。不过还不错。谢谢。

63307 次浏览

我赞成做梦理论。

你不能传递一个函数: System 的导数。委托(也就是 lambdas)不实现那些接口。

您可能看到的是 Converter<TInput, TOutput>委托的使用,它可以由 lambda 建模。数组使用此委托的一个实例。

您可以为 Array 提供一个 lambda。方法,因为它需要一个接受两个 T 类型的对象并返回一个整数的方法。因此,您可以提供以下定义 (a, b) => a.CompareTo(b)的 lambda。一个对整数数组进行降序排序的示例:

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


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

这些方法没有接受委托而不是接口的重载,但是:

  • You can normally return a simpler sort key through the delegate you pass to Enumerable.OrderBy
  • 同样,您可以在调用 Enumerable.SequenceEqual之前调用 Enumerable.Select
  • 编写一个根据 Func<T, T, bool>实现 IEqualityComparer<T>的包装器应该很简单
  • F# lets you implement this sort of interface in terms of a lambda :)

您不能直接传递它,但是您可以通过定义一个除了 Func<T,T,int>之外的 LambdaComparer类,然后在它的 CompareTo中使用它。

它没有那么简洁,但是你可以通过 Func上的一些创造性的扩展方法使它变得更短。

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

我不太确定它到底有什么用,因为我认为对于基本库中大多数期望使用 IComparer 的情况来说,都会有一个需要进行比较的过载... ... 但仅供记录:

在.Net 4.5中,他们添加了一个从比较中获得 IComparer 的方法: 比较,创造

这样你就可以把你的 lambda 传递给它并获得一个 IComparer。

我也在网上搜索解决方案,但没有找到任何令人满意的解决方案。所以我创建了一个通用的 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();
}
}

如果你需要这个函数来处理 lambda 和两种不同的元素类型:

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