使用LINQ to Objects查找一个集合中与另一个集合不匹配的项目

Tru*_*ill 23 c# linq linq-to-objects

我想在一个集合中找到与另一个集合不匹配的所有项目.但是,这些集合的类型不同; 我想编写一个lambda表达式来指定相等性.

一个LINQPad我正在尝试做的例子:

void Main()
{
    var employees = new[]
    {
        new Employee { Id = 20, Name = "Bob" },
        new Employee { Id = 10, Name = "Bill" },
        new Employee { Id = 30, Name = "Frank" }
    };

    var managers = new[]
    {
        new Manager { EmployeeId = 20 },
        new Manager { EmployeeId = 30 }
    };

    var nonManagers =
    from employee in employees
    where !(managers.Any(x => x.EmployeeId == employee.Id))
    select employee;

    nonManagers.Dump();

    // Based on cdonner's answer:

    var nonManagers2 =
    from employee in employees
    join manager in managers
        on employee.Id equals manager.EmployeeId
    into tempManagers
    from manager in tempManagers.DefaultIfEmpty()
    where manager == null
    select employee;

    nonManagers2.Dump();

    // Based on Richard Hein's answer:

    var nonManagers3 =
    employees.Except(
        from employee in employees
        join manager in managers
            on employee.Id equals manager.EmployeeId
        select employee);

    nonManagers3.Dump();
}

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Manager
{
    public int EmployeeId { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

以上工作,将返回员工账单(#10).但它看起来并不优雅,而且对于较大的集合来说可能效率低下.在SQL中,我可能会执行LEFT JOIN并查找第二个ID为NULL的项目.在LINQ中执行此操作的最佳做​​法是什么?

编辑:更新以防止依赖于Id等于索引的解决方案.

编辑:添加了cdonner的解决方案 - 任何人有什么更简单的?

编辑:在Richard Hein的答案中添加了一个变体,我目前最喜欢的.感谢大家的一些优秀答案!

Ric*_*ein 30

这与其他一些示例几乎相同,但代码较少:

employees.Except(employees.Join(managers, e => e.Id, m => m.EmployeeId, (e, m) => e));
Run Code Online (Sandbox Code Playgroud)

然而,它并不比employees.Where(e =>!managers.Any(m => m.EmployeeId == e.Id))或原始语法更简单.


cdo*_*ner 5

    /// <summary>
    /// This method returns items in a set that are not in 
    /// another set of a different type
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TOther"></typeparam>
    /// <typeparam name="TKey"></typeparam>
    /// <param name="items"></param>
    /// <param name="other"></param>
    /// <param name="getItemKey"></param>
    /// <param name="getOtherKey"></param>
    /// <returns></returns>
    public static IEnumerable<T> Except<T, TOther, TKey>(
                                           this IEnumerable<T> items,
                                           IEnumerable<TOther> other,
                                           Func<T, TKey> getItemKey,
                                           Func<TOther, TKey> getOtherKey)
    {
        return from item in items
               join otherItem in other on getItemKey(item)
               equals getOtherKey(otherItem) into tempItems
               from temp in tempItems.DefaultIfEmpty()
               where ReferenceEquals(null, temp) || temp.Equals(default(TOther))
               select item;
    }
Run Code Online (Sandbox Code Playgroud)

我不记得我在哪里找到这种方法.


Par*_*ury 5

__PRE__