67

因此,.NET 3.0/3.5 为我们提供了许多查询、排序和操作数据的新方法,这要归功于 LINQ 提供的所有简洁功能。有时,我需要比较没有内置比较运算符的用户定义类型。在许多情况下,比较非常简单——比如 foo1.key ?= foo2.key。我可以使用匿名委托/lambda 函数简单地指定内联比较,而不是为该类型创建一个新的 IEqualityComparer 吗?就像是:

var f1 = ...,
    f2 = ...;
var f3 = f1.Except(
           f2, new IEqualityComparer(
             (Foo a, Foo b) => a.key.CompareTo(b.key)
           ) );

我很确定上述方法实际上不起作用。我只是不想为了告诉程序如何将苹果与苹果进行比较而使整个班级变得“沉重”。

4

9 回答 9

74

我的MiscUtil库包含一个 ProjectionComparer,用于从投影委托构建 IComparer<T>。让 ProjectionEqualityComparer 做同样的事情需要 10 分钟。

编辑:这是 ProjectionEqualityComparer 的代码:

using System;
using System.Collections.Generic;

/// <summary>
/// Non-generic class to produce instances of the generic class,
/// optionally using type inference.
/// </summary>
public static class ProjectionEqualityComparer
{
    /// <summary>
    /// Creates an instance of ProjectionEqualityComparer using the specified projection.
    /// </summary>
    /// <typeparam name="TSource">Type parameter for the elements to be compared</typeparam>
    /// <typeparam name="TKey">Type parameter for the keys to be compared,
    /// after being projected from the elements</typeparam>
    /// <param name="projection">Projection to use when determining the key of an element</param>
    /// <returns>A comparer which will compare elements by projecting 
    /// each element to its key, and comparing keys</returns>
    public static ProjectionEqualityComparer<TSource, TKey> Create<TSource, TKey>(Func<TSource, TKey> projection)
    {
        return new ProjectionEqualityComparer<TSource, TKey>(projection);
    }

    /// <summary>
    /// Creates an instance of ProjectionEqualityComparer using the specified projection.
    /// The ignored parameter is solely present to aid type inference.
    /// </summary>
    /// <typeparam name="TSource">Type parameter for the elements to be compared</typeparam>
    /// <typeparam name="TKey">Type parameter for the keys to be compared,
    /// after being projected from the elements</typeparam>
    /// <param name="ignored">Value is ignored - type may be used by type inference</param>
    /// <param name="projection">Projection to use when determining the key of an element</param>
    /// <returns>A comparer which will compare elements by projecting
    /// each element to its key, and comparing keys</returns>
    public static ProjectionEqualityComparer<TSource, TKey> Create<TSource, TKey>
        (TSource ignored,
         Func<TSource, TKey> projection)
    {
        return new ProjectionEqualityComparer<TSource, TKey>(projection);
    }

}

/// <summary>
/// Class generic in the source only to produce instances of the 
/// doubly generic class, optionally using type inference.
/// </summary>
public static class ProjectionEqualityComparer<TSource>
{
    /// <summary>
    /// Creates an instance of ProjectionEqualityComparer using the specified projection.
    /// </summary>
    /// <typeparam name="TKey">Type parameter for the keys to be compared,
    /// after being projected from the elements</typeparam>
    /// <param name="projection">Projection to use when determining the key of an element</param>
    /// <returns>A comparer which will compare elements by projecting each element to its key,
    /// and comparing keys</returns>        
    public static ProjectionEqualityComparer<TSource, TKey> Create<TKey>(Func<TSource, TKey> projection)
    {
        return new ProjectionEqualityComparer<TSource, TKey>(projection);
    }
}

/// <summary>
/// Comparer which projects each element of the comparison to a key, and then compares
/// those keys using the specified (or default) comparer for the key type.
/// </summary>
/// <typeparam name="TSource">Type of elements which this comparer 
/// will be asked to compare</typeparam>
/// <typeparam name="TKey">Type of the key projected
/// from the element</typeparam>
public class ProjectionEqualityComparer<TSource, TKey> : IEqualityComparer<TSource>
{
    readonly Func<TSource, TKey> projection;
    readonly IEqualityComparer<TKey> comparer;

    /// <summary>
    /// Creates a new instance using the specified projection, which must not be null.
    /// The default comparer for the projected type is used.
    /// </summary>
    /// <param name="projection">Projection to use during comparisons</param>
    public ProjectionEqualityComparer(Func<TSource, TKey> projection)
        : this(projection, null)
    {
    }

    /// <summary>
    /// Creates a new instance using the specified projection, which must not be null.
    /// </summary>
    /// <param name="projection">Projection to use during comparisons</param>
    /// <param name="comparer">The comparer to use on the keys. May be null, in
    /// which case the default comparer will be used.</param>
    public ProjectionEqualityComparer(Func<TSource, TKey> projection, IEqualityComparer<TKey> comparer)
    {
        if (projection == null)
        {
            throw new ArgumentNullException("projection");
        }
        this.comparer = comparer ?? EqualityComparer<TKey>.Default;
        this.projection = projection;
    }

    /// <summary>
    /// Compares the two specified values for equality by applying the projection
    /// to each value and then using the equality comparer on the resulting keys. Null
    /// references are never passed to the projection.
    /// </summary>
    public bool Equals(TSource x, TSource y)
    {
        if (x == null && y == null)
        {
            return true;
        }
        if (x == null || y == null)
        {
            return false;
        }
        return comparer.Equals(projection(x), projection(y));
    }

    /// <summary>
    /// Produces a hash code for the given value by projecting it and
    /// then asking the equality comparer to find the hash code of
    /// the resulting key.
    /// </summary>
    public int GetHashCode(TSource obj)
    {
        if (obj == null)
        {
            throw new ArgumentNullException("obj");
        }
        return comparer.GetHashCode(projection(obj));
    }
}

这是一个示例使用:

var f3 = f1.Except(f2, ProjectionEqualityComparer<Foo>.Create(a => a.key));
于 2008-10-09T16:43:50.827 回答
22

这是一个简单的辅助类,应该做你想做的

public class EqualityComparer<T> : IEqualityComparer<T>
{
    public EqualityComparer(Func<T, T, bool> cmp)
    {
        this.cmp = cmp;
    }
    public bool Equals(T x, T y)
    {
        return cmp(x, y);
    }

    public int GetHashCode(T obj)
    {
        return obj.GetHashCode();
    }

    public Func<T, T, bool> cmp { get; set; }
}

你可以像这样使用它:

processed.Union(suburbs, new EqualityComparer<Suburb>((s1, s2)
    => s1.SuburbId == s2.SuburbId));
于 2012-05-23T12:46:04.883 回答
8

我发现在 IEnumerable 上提供额外的帮助是一种更简洁的方法。

见:这个问题

所以你可以有:

var f3 = f1.Except(
           f2, 
             (a, b) => a.key.CompareTo(b.key)
            );

如果您正确定义了扩展方法

于 2009-04-13T07:06:01.300 回答
7

为什么不这样:

    public class Comparer<T> : IEqualityComparer<T>
    {
        private readonly Func<T, T, bool> _equalityComparer;

        public Comparer(Func<T, T, bool> equalityComparer)
        {
            _equalityComparer = equalityComparer;
        }

        public bool Equals(T first, T second)
        {
            return _equalityComparer(first, second);
        }

        public int GetHashCode(T value)
        {
            return value.GetHashCode();
        }
    }

然后你可以做一些类似的事情(例如在 in 的情况下IntersectIEnumerable<T>

list.Intersect(otherList, new Comparer<T>( (x, y) => x.Property == y.Property));

该类Comparer可以放在实用程序项目中并在需要的地方使用。

我现在才看到 Sam Saffron 的答案(与这个非常相似)。

于 2015-04-01T14:24:19.310 回答
6

这个项目做了类似的事情:AnonymousComparer - Linq 的 lambda 比较选择器,它也有 LINQ 标准查询运算符的扩展。

于 2014-01-23T22:18:37.640 回答
5

所以我知道这是您问题的解决方法,但是当我发现我遇到了您在这里遇到的情况(组合列表和过滤重复项),并且 Distinct 需要一个我没有的 IEquityComparer 时,我通常会去使用 Concat -> Group -> Select。

原来的

var f1 = ...,
    f2 = ...;
var f3 = f1.Except(
           f2, new IEqualityComparer(
             (Foo a, Foo b) => a.key.CompareTo(b.key)
           ) );

新的

var f1 = ...,
    f2 = ...;
var distinctF = f1
    .Concat(f2)                       // Combine the lists
    .GroupBy(x => x.key)              // Group them up by our equity comparison key
    .Select(x => x.FirstOrDefault()); // Just grab one of them.

请注意,在 GroupBy() 中,您有机会添加逻辑来创建混合键,例如:

.GroupBy(f => new Uri(f.Url).PathAndQuery)  

以及在 Select() 中,如果您想指定结果项目来自哪个列表,您可以说:

.Select(x => x.FirstOrDefault(y => f1.Contains(y))

希望有帮助!

于 2020-02-18T14:53:50.470 回答
1

对于小型套装,您可以执行以下操作:

f3 = f1.Where(x1 => f2.All(x2 => x2.key != x1.key));

对于大型集,您将需要更高效的搜索,例如:

var tmp = new HashSet<string>(f2.Select(f => f.key));
f3 = f1.Where(f => tmp.Add(f.key));

但是,在这里,Typeof必须实现IEqualityComparer(上面我假设它是 a string)。因此,这并不能真正回答您关于在这种情况下使用 lambda 的问题,但它使用的代码确实比一些答案要少。

您可能会依赖优化器并将第二个解决方案缩短为:

f3 = f1.Where(x1 => (new HashSet<string>(f2.Select(x2 => x2.key))).Add(x1.key));

但是,我还没有运行测试以知道它是否以相同的速度运行。而那一个班轮可能太聪明了,无法维护。

于 2015-10-23T13:25:12.733 回答
1

在其他答案的基础上,创建通用比较器是我最喜欢的。但是我遇到了 Linq Enumerable.Unionmsdn .Net 参考)的问题,即它直接使用 GetHashCode 而不考虑 Equals 覆盖。

这使我将比较器实现为:

public class Comparer<T> : IEqualityComparer<T>
{
    private readonly Func<T, int> _hashFunction;

    public Comparer(Func<T, int> hashFunction)
    {
        _hashFunction = hashFunction;
    }

    public bool Equals(T first, T second)
    {
        return _hashFunction(first) == _hashFunction(second);
    }

    public int GetHashCode(T value)
    {
        return _hashFunction(value);
    }
}

像这样使用它:

list.Union(otherList, new Comparer<T>( x => x.StringValue.GetHashCode()));

请注意,比较可能会产生一些误报,因为被比较的信息被映射到一个int值。

于 2019-06-12T11:35:26.140 回答
-1

像其他答案一样,但更简洁的 c# 7:

public class LambdaComparer<T> : IEqualityComparer<T> {
  private readonly Func<T, T, bool> lambdaComparer;
  private readonly Func<T, int> lambdaHash;
  public LambdaComparer(Func<T, T, bool> lambdaComparer) : this(lambdaComparer, o => o.GetHashCode()) {}
  public LambdaComparer(Func<T, T, bool> lambdaComparer, Func<T, int> lambdaHash) { this.lambdaComparer = lambdaComparer; this.lambdaHash = lambdaHash; }
  public bool Equals(T x, T y) => lambdaComparer is null ? false : lambdaComparer(x, y);
  public int GetHashCode(T obj) => lambdaHash is null ? 0 : lambdaHash(obj);
}

然后:

var a=List<string> { "a", "b" };
var b=List<string> { "a", "*" };
return a.SequenceEquals(b, new LambdaComparer<string>((s1, s2) => s1 is null ? s2 is null : s1 == s2 || s2 == "*");  
于 2018-07-26T23:15:48.067 回答