【问题标题】:Can I use Linq's Except() with a lambda expression comparer?我可以将 Linq 的 except() 与 lambda 表达式比较器一起使用吗?
【发布时间】:2026-01-17 08:20:06
【问题描述】:

我知道我可以调用 linq 的 except 并指定一个自定义的 IEqualityComparer,但是为此目的为每种数据类型实现一个新的 Comparer 类似乎有点过头了。是否可以使用 lambda 表达式来提供相等函数,例如使用 Where 或其他 LINQ 函数时?

如果我不能,是否有替代方案?

【问题讨论】:

    标签: c# .net linq


    【解决方案1】:

    对于任何仍在寻找的人;这是实现自定义 lambda 比较器的另一种方式。

    public class LambdaComparer<T> : IEqualityComparer<T>
        {
            private readonly Func<T, T, bool> _expression;
    
            public LambdaComparer(Func<T, T, bool> lambda)
            {
                _expression = lambda;
            }
    
            public bool Equals(T x, T y)
            {
                return _expression(x, y);
            }
    
            public int GetHashCode(T obj)
            {
                /*
                 If you just return 0 for the hash the Equals comparer will kick in. 
                 The underlying evaluation checks the hash and then short circuits the evaluation if it is false.
                 Otherwise, it checks the Equals. If you force the hash to be true (by assuming 0 for both objects), 
                 you will always fall through to the Equals check which is what we are always going for.
                */
                return 0;
            }
        }
    

    然后你可以为 linq 创建一个扩展,除了一个接受 lambda 的 Intersect

    /// <summary>
            /// Returns all items in the first collection except the ones in the second collection that match the lambda condition
            /// </summary>
            /// <typeparam name="T">The type</typeparam>
            /// <param name="listA">The first list</param>
            /// <param name="listB">The second list</param>
            /// <param name="lambda">The filter expression</param>
            /// <returns>The filtered list</returns>
            public static IEnumerable<T> Except<T>(this IEnumerable<T> listA, IEnumerable<T> listB, Func<T, T, bool> lambda)
            {
                return listA.Except(listB, new LambdaComparer<T>(lambda));
            }
    
            /// <summary>
            /// Returns all items in the first collection that intersect the ones in the second collection that match the lambda condition
            /// </summary>
            /// <typeparam name="T">The type</typeparam>
            /// <param name="listA">The first list</param>
            /// <param name="listB">The second list</param>
            /// <param name="lambda">The filter expression</param>
            /// <returns>The filtered list</returns>
            public static IEnumerable<T> Intersect<T>(this IEnumerable<T> listA, IEnumerable<T> listB, Func<T, T, bool> lambda)
            {
                return listA.Intersect(listB, new LambdaComparer<T>(lambda));
            }
    

    用法:

    var availableItems = allItems.Except(filterItems, (p, p1) => p.Id== p1.Id);
    

    【讨论】:

      【解决方案2】:

      你能不能用 .Where 和 lambda 过滤掉你需要的值?

      根据要求提供示例:

          static void Main(string[] args)
          {
              var firstCustomers = new[] { new Customer { Id = 1, Name = "Bob" }, new Customer { Id = 2, Name = "Steve" } };
              var secondCustomers = new[] { new Customer { Id = 2, Name = "Steve" }, new Customer { Id = 3, Name = "John" } };
      
              var customers = secondCustomers.Where(c => !firstCustomers.Select(fc => fc.Id).Contains(c.Id));
          }
      
          public class Customer
          {
              public int Id { get; set; }
              public string Name { get; set; }
          }
      

      【讨论】:

      • 我该怎么做?我想过滤掉 id 没有出现在另一个集合中的项目。
      • 这个实现在计算上是昂贵的 - 对于 secondCustomers 中的每个客户,它可能会评估 firstCustomers 中的每个客户。由于复杂度为 O(n^2),因此当集合很大时,这将是显而易见的。这就是 LINQ 中的 Intersect 运算符使用 Set 的原因,使用提供的相等比较器作为种子,该比较器使用 GetHashCode。
      • 当您必须检查 2 个属性时,您会怎么做? (无 ID)
      • 如果您仍然喜欢这种方法,对于长列表,我建议在 firstCustomers 集合上使用 Select(fc =&gt; fc.Id).ToHashSet(),而不是在 lambda 表达式中对结果集进行 O(1) 查找。
      【解决方案3】:

      我认为您不能直接使用基本的 LINQ 接口,但我看到人们使用扩展方法实现 LambdaComparer 类,这将帮助您做到这一点。

      Here's an example

      【讨论】:

      • 我有什么理由需要实现 GetHash 吗?
      • 链接好像失效了
      【解决方案4】:

      这是我整理的一些简单的东西:

      public class CustomComparer<TSource, TCompareType> : IEqualityComparer<TSource> where TSource : class 
      {
          private readonly Func<TSource, TCompareType> getComparisonObject;
          public CustomComparer(Func<TSource,TCompareType> getComparisonObject)
          {
              if (getComparisonObject == null) throw new ArgumentNullException("getComparisonObject");
              this.getComparisonObject = getComparisonObject;
          } 
      
          /// <summary>
          /// Determines whether the specified objects are equal.
          /// </summary>
          /// <returns>
          /// true if the specified objects are equal; otherwise, false.
          /// </returns>
          /// <param name="x">The first object of type <paramref name="T"/> to compare.
          ///                 </param><param name="y">The second object of type <paramref name="T"/> to compare.
          ///                 </param>
          public bool Equals(TSource x, TSource y)
          {
              if (x == null)
              {
                  return (y == null);
              }
              else if (y == null)
              {
                  return false;
              }
              return EqualityComparer<TCompareType>.Default.Equals(getComparisonObject(x), getComparisonObject(y));
          }
      
          /// <summary>
          /// Returns a hash code for the specified object.
          /// </summary>
          /// <returns>
          /// A hash code for the specified object.
          /// </returns>
          /// <param name="obj">The <see cref="T:System.Object"/> for which a hash code is to be returned.
          ///                 </param><exception cref="T:System.ArgumentNullException">The type of <paramref name="obj"/> is a reference type and <paramref name="obj"/> is null.
          ///                 </exception>
          public int GetHashCode(TSource obj)
          {
              return EqualityComparer<TCompareType>.Default.GetHashCode(getComparisonObject(obj));
          }
      }
      

      用法:

      var myItems = allItems.Except(theirItems, new CustomComparer(item => item.Name));
      

      【讨论】:

        【解决方案5】:

        使用extension

        public static IEnumerable<T> Except<T, TKey>(this IEnumerable<T> items, IEnumerable<T> other, 
                                                                                    Func<T, TKey> getKey)
        {
            return from item in items
                   join otherItem in other on getKey(item)
                   equals getKey(otherItem) into tempItems
                   from temp in tempItems.DefaultIfEmpty()
                   where ReferenceEquals(null, temp) || temp.Equals(default(T))
                   select item;
        
        }
        

        Source

        【讨论】:

          【解决方案6】:

          Here' 是 l except r 解决方案,基于 LINQ 外连接 技术:

          from l in new[] { 1, 2, 3 }
          join r in new[] { 2, 4, 5 }
          on l equals r
          into rr
          where !rr.Any()
          select l
          

          将产生:1、3

          【讨论】:

            【解决方案7】:

            但是你可以试试这个。这很容易,我认为代码包含错误。当然,代码是少量的,没有LINQ转换为db等。

            public static IEnumerable<TSource> ExceptPredicate<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second, Func<TSource, TSource, bool> compare) {
              foreach (var itmFirst in first) {
                if (!second.Any(itmsecond => compare(itmFirst, itmsecond))) {
                  yield return itmFirst;
                }
              }
              yield break;
            }
            

            【讨论】:

            • 请注意Any() 是一个循环,因此它是另一个循环中的一个循环。这意味着复杂度是O(n^2)
            最近更新 更多