可以在IComparer或IEqualityComparer或任何单方法接口的位置上传递lambda表达式吗?

79

我看到一些代码,其中这个人将lambda表达式传递给了一个期望IComparer或IEqualityComparer的ArrayList.Sort(IComparer here)或IEnumerable.SequenceEqual(IEnumerable list, IEqualityComparer here)。

不确定我是否看到过,可能只是梦想。我在这些集合中似乎找不到接受Func<>或委托的扩展方法。

是否有这样的重载/扩展方法?如果没有,是否可以像这样混合使用,将算法(读取委托)传递给预期单方法接口的位置?

更新 谢谢大家。这就是我想的。我知道怎么写转换。我只是不确定自己是否看到过这样的代码。

再次更新 看,这里,我发现了一个这样的实例。我没做梦。看看这个人在这里做了什么。这是怎么回事?

还有一个更新: 好的,我明白了。那个人正在使用Comparison<T>重载。很好。很好,但很容易误导你。还是很好的,谢谢。


1
可能是将委托包装在IEqualityComparer中的重复问题。 - nawfal
@nawfal:那是一个不同的问题。它们有些相关但仍然不同。虽然如此,这是一个非常好的问题。感谢分享。我觉得它非常有趣。 :-) - Water Cooler v2
哦,我现在明白了,但是很接近 :P 我撤回了关闭投票,但我会保留评论以便其他访问者注意到。还有一件事,请接受答案。我认为得票最高的答案回答了你的问题。 - nawfal
8个回答

38

我不太确定它真正有多少用处,因为我认为对于大多数情况在基础库中期望一个 IComparer 的情况下,有一个重载函数期望一个 Comparison...但只是为了记录:

.Net 4.5 添加了一个从 Comparison 获取 IComparer 的方法:Comparer.Create

所以你可以将 lambda 传递给它并获取 IComparer。


在.NET 4中添加一个扩展方法来完成相同的事情是相对简单的。 - jessehouwing
12
有没有类似的东西适用于EqualityComparer?它没有一个Create方法,但是为Comparer添加这个有用的方法而不是EqualityComparer似乎非常奇怪。 - rdans
6
EqualityComparer 使用了不同的签名,同时还使用了 GetHashCode 方法。因此,您无法轻松地为其创建比较器。 - VMAtm

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

13

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

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

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

你能解释一下更多吗?这是IComparer<T>接口特有的行为还是适用于任何只有一个方法的接口? - StriplingWarrior
7
@Stripling,我认为实际上使用了接受Comparison<T>的重载,因为Comparison是一个接受两个参数并返回整数的委托。因此,提供有效的lambda表达式符合此重载。 - Anthony Pegram

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

仅通过使用 Func<T, TKey> 就可以实现,但感谢您展示如何使用表达式。 - marbel82

5

你不能直接传递它,但是你可以通过定义一个LambdaComparer类来实现,该类接受一个Func<T,T,int>,然后在其CompareTo中使用它。

虽然不是很简洁,但你可以通过一些创造性的Func扩展方法使它更短。


4

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

  • 通常可以通过传递给Enumerable.OrderBy的委托返回一个更简单的排序键。
  • 同样,在调用Enumerable.SequenceEqual之前,可以调用Enumerable.Select
  • 编写一个包装器,以Func<T,T,bool>为基础实现IEqualityComparer<T>应该很容易。
  • F#允许您使用lambda表达式来实现这种类型的接口:)

3

如果您需要将此功能用于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;
    }
}

3

我支持梦想理论。

在期望对象的位置上,您不能传递函数:System.Delegate的导数(这就是lambda所代表的内容)没有实现那些接口。

您可能看到的是使用Converter<TInput, TOutput>委托的用法,它可以由lambda建模。 Array.ConvertAll使用此委托的实例。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接