我可以内联指定我的显式类型比较器吗?

Posted

技术标签:

【中文标题】我可以内联指定我的显式类型比较器吗?【英文标题】:Can I specify my explicit type comparator inline? 【发布时间】:2010-09-16 07:57:00 【问题描述】:

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

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

我很确定上述内容实际上不起作用。我只是不想为了告诉程序如何将苹果与苹果进行比较而不得不为整个班级做出“沉重”的事情。

【问题讨论】:

对于任何只是在寻找语言是否支持这个问题的答案的人,答案是否定的。它需要一些聪明的自定义类。 奇怪的是,他们添加了Comparer<T>.Create 来创建一个比较器,但没有EqualityComparer<T>.Create(),这是我们需要的。还要注意这里的一些评论,讨论是实现接口还是从 EqualityComparer 派生新类:docs.microsoft.com/en-us/dotnet/api/… 【参考方案1】:

所以我知道这是您的问题的一种解决方法,但是当我发现我遇到了您在这里遇到的情况(组合列表和过滤重复项),并且 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))

希望有帮助!

【讨论】:

【参考方案2】:

在其他答案的基础上创建通用比较器是我最喜欢的。但是我遇到了 Linq Enumerable.Union (msdn .Net reference) 的问题,它直接使用 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 值。

【讨论】:

【参考方案3】:

像其他答案一样,但更简洁的 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 == "*");  

【讨论】:

【参考方案4】:

对于小型套装,您可以这样做:

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

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

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

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

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

【讨论】:

【参考方案5】:

为什么不这样:

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

然后您可以执行类似的操作(例如,在 IEnumerable&lt;T&gt; 中的 Intersect 的情况下):

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

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

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

【讨论】:

我认为框架包含的比较器可以与这种方法结合使用(例如由 StringComparer 提供的比较器:msdn.microsoft.com/en-us/library/…)是不值得的。【参考方案6】:

这个项目做了类似的事情:AnonymousComparer - lambda compare selector for Linq,它也有 LINQ 标准查询运算符的扩展。

【讨论】:

这确实值得更多的支持。我在使用 Lambda / Linq 时遇到的最方便的库之一【参考方案7】:

这是一个简单的帮助类,应该可以做你想做的事情

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

【讨论】:

这不起作用,因为 Union 和 Distinct 首先检查哈希码,无论代表说什么,它都可能不同。将 GetHashCode 更改为始终返回 0 可以解决问题。 @makhdumi 这可能看起来像是一个“修复”,但它也会通过将这些方法转换为 O(n^2) 操作来破坏这些方法的性能。正确实施GetHashCode 很重要。无法使用此答案使用的 cmp 函数进行正确实现。这个答案和上述评论都存在危险的缺陷。 @jonskeets 的回答要好得多。 @MgSam 你是对的。我不记得我为什么要走这条路,但正如你所说,散列对于检查集至关重要。各位,请不要照我说的做。 如果您只使用小型集合,则覆盖 GetHashCode 是可以的,但是如果您使用大型集合,那么您确实需要正确实现 GetHashCode。【参考方案8】:

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

见:this question

所以你可以:

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

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

【讨论】:

我希望我们可以在没有扩展方法的情况下做到这一点【参考方案9】:

我的MiscUtil 库包含一个 ProjectionComparer,用于从投影委托构建 IComparer。让 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));

【讨论】:

虽然我很欣赏这个链接,但我认为如果你可以在 SO 上粘贴一个 sn-p 而不是需要转到您的站点,下载并解压缩源代码,然后翻页查找特定课程。请问? 我会同时做这两个 - 但由于方便的方法等原因,它可能会是相当多的代码。 现在唯一的问题是,为什么这种东西没有内置到语言中? @Coderer:我真的不希望它被内置到语言中。它更像是一个框架的东西。不过,如果将“ExceptBy”等作为 LINQ to Objects 的额外部分,那就太好了。 @Jon 例如。 f1.Except(f2, (a, b) => a.key.CompareTo(b.key));不需要投影的相等比较器,对于 l.Distinct(a=>a.key) 等...

以上是关于我可以内联指定我的显式类型比较器吗?的主要内容,如果未能解决你的问题,请参考以下文章

使用 DataContractSerializer 的接口中的显式类型

我应该如何确定查询返回到 GraphQL 接口类型的显式类型?

正确使用函数的显式模板实例化?

类型类函数的显式 forall

真的没有来自 std::string_view 的 std::string 的显式构造函数吗?

Scala 中的显式类型转换