代码之家  ›  专栏  ›  技术社区  ›  Coderer

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

  •  69
  • Coderer  · 技术社区  · 17 年前

    所以。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)
               ) );
    

    我很确定上面的方法实际上不起作用。我只是不想为了告诉程序如何比较苹果和苹果而做一个像全班一样“重”的东西。

    9 回复  |  直到 17 年前
        1
  •  74
  •   Jon Skeet    17 年前

    我的 MiscUtil 库中包含一个ProjectonComparer,用于构建IComparer<T>来自投影代表。制作一个ProjectionQualityComparer来做同样的事情需要10分钟的时间。

    编辑:这是ProjectionQualityComparer的代码:

    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));
    
        2
  •  22
  •   ahsteele tungi52    12 年前

    这里有一个简单的助手类,它应该做你想做的事情

    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));
    
        3
  •  8
  •   Community CDub    8 年前

    我发现在IEnumerable上提供额外的帮助程序是一种更干净的方法。

    请参阅: this question

    所以你可以:

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

    如果正确定义了扩展方法

        4
  •  7
  •   Tamas Ionut    10 年前

    为什么不像这样:

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

    然后你可以做一些类似的事情(例如,在 Intersect 在……里面 IEnumerable<T> ):

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

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

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

        5
  •  6
  •   Jeremy Thomas    11 年前

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

    原来的

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

    希望这能有所帮助!

        6
  •  5
  •   WhiteleyJ    5 年前

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

        7
  •  1
  •   mheyman    9 年前

    对于小集合,您可以执行以下操作:

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

    但是,在这里 Type 属于的 钥匙 必须执行 IEqualityComparer (上面我假设这是一个 string ).所以,这并不能真正回答你在这种情况下使用lambda的问题,但它确实比一些答案使用更少的代码。

    您可以依赖优化器,将第二个解决方案缩短为:

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

    但是,我还没有运行测试来了解它是否以相同的速度运行。而且那条线可能太聪明了,无法维护。

        8
  •  1
  •   OriolBG    6 年前

    在其他答案的基础上,创建一个通用的比较器是我最喜欢的。但我和林有点问题 Enumerable.Union ( msdn .Net reference )这是因为它直接使用GetHashCode,而不考虑Equals覆盖。

    这让我将Comparer实现为:

    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 价值。

        9
  •  -1
  •   kofifus    7 年前

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