【问题标题】:Pass a lambda expression in place of IComparer or IEqualityComparer or any single-method interface?传递一个 lambda 表达式来代替 IComparer 或 IEqualityComparer 或任何单一方法接口?
【发布时间】:2010-07-06 20:13:29
【问题描述】:

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

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

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

更新 感谢大家。那正是我所想。我一定是在做梦。我知道如何编写转换。我只是不确定我是否看到过类似的东西,或者只是认为我看到过。

又一次更新 看,在这里,我发现了一个这样的例子。毕竟我不是在做梦。看what this guy is doing here。什么给了?

还有一个更新: 好的我明白了。这家伙正在使用Comparison<T> 重载。好的。很好,但很容易误导你。不过不错。谢谢。

【问题讨论】:

  • @nawfal:这是一个不同的问题。它们有些相关,但仍然不同。不过,这是一个非常好的问题。感谢分享。我觉得很有趣。 :-)
  • 哦,是的,我现在看到了,但非常接近 :P 我撤回了接近投票,但我会保留评论,以便其他访问者注意到。一件事,请接受答案。我认为票数最高的答案回答了您的问题。

标签: linq extension-methods ienumerable iequalitycomparer icomparer


【解决方案1】:

我不太确定它到底有什么用处,因为我认为对于期望 IComparer 的基本库中的大多数情况,存在需要比较的重载......但只是为了记录:

在 .Net 4.5 中,他们添加了一种从比较中获取 IComparer 的方法: Comparer.Create

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

【讨论】:

  • 在 .NET 4 中添加一个扩展方法来完成同样的事情会相对简单。
  • EqualityComparer 有类似的东西吗?它没有 Create 方法,但是为 Comparer 而不是 EqualityComparer 添加这个有用的方法似乎很奇怪
  • @rdans EqualityComparer 使用不同的签名,也使用GetHashCode 方法。所以你不能轻易地为此创建比较器。
【解决方案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】:

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

    int[] array = { 1, 8, 19, 4 };
    
    // descending sort 
    Array.Sort(array, (a, b) => -1 * a.CompareTo(b));
    

    【讨论】:

    • 您能进一步解释一下吗?这是 IComparer 接口独有的行为,还是适用于任何只有一个方法的接口?
    • @Stripling,我相信这实际上是在使用接受Comparison&lt;T&gt; 的重载,因为Comparison 是一个接受两个参数并返回整数的委托。因此,提供有效的 lambda 符合此重载的条件。
    【解决方案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&lt;T,T,int&gt; 然后在它的 CompareTo 中使用它。

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

    【讨论】:

      【解决方案6】:

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

      • 您通常可以通过传递给Enumerable.OrderBy 的委托返回更简单的排序键
      • 同样,您可以先拨打Enumerable.Select,然后再拨打Enumerable.SequenceEqual
      • 按照Func&lt;T, T, bool&gt; 编写实现IEqualityComparer&lt;T&gt; 的包装器应该很简单
      • F# 允许您根据 lambda 实现这种接口:)

      【讨论】:

        【解决方案7】:

        我投票支持做梦理论。

        您不能在需要对象的地方传递函数:System.Delegate 的派生类(即 lambda)不实现这些接口。

        您可能看到的是 Converter&lt;TInput, TOutput&gt; 委托的使用,它可以由 lambda 建模。 Array.ConvertAll 使用此委托的一个实例。

        【讨论】:

          【解决方案8】:

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

          【讨论】:

            猜你喜欢
            • 2013-05-26
            • 1970-01-01
            • 2021-06-08
            • 1970-01-01
            • 1970-01-01
            • 2011-06-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多