使用LINQ通过一个属性比较两个列表

59

假设我有以下内容:

    class Widget1{
        public int TypeID { get; set; }
        public string Color { get; set; }
    }

    class Widget2
    {
        public int TypeID { get; set; }
        public string Brand { get; set; }
    }

    private void test()
    {
        List<Widget1> widgets1 = new List<Widget1>();
        List<Widget2> widgets2 = new List<Widget2>();
        List<Widget1> widgets1_in_widgets2 = new List<Widget1>();

        //some code here to populate widgets1 and widgets2

        foreach (Widget1 w1 in widgets1)
        {
            foreach (Widget2 w2 in widgets2)
            {
                if (w1.TypeID == w2.TypeID)
                {
                    widgets1_in_widgets2.Add(w1);
                }
            }
        }
    }

我正在使用两个foreach循环通过TypeID比较列表以填充第三个列表。 是否有其他方法使用LINQ通过TypeID比较这两个列表?也许使用Intersect或其他一些函数?

6个回答

64

你可以做到这件事

widgets2.Where(y=>widget1.Any(z=>z.TypeID==y.TypeID));

7
尽管这将生成正确的输出,但性能相当差,因为您正在对一个集合中的每个项进行线性搜索以匹配另一个集合中的项。 - Servy
1
最简单的更改就是将其中一个集合的TypeIDs存储在一个HashSet中,这样可以更快地进行搜索。更重要的更改是只使用Join,如我所示,尽管在底层它实际上会执行几乎相同的操作。 - Servy
1
查询良好,如果在小列表中使用,不应该有问题。 - Yasser Shaikh
好的和更快的一个 - Vishal Deshmukh

52

你需要的是一个Join

var widgets1_in_widgets2 = from first in widgest1
    join second in widgets2
    on first.TypeID equals second.TypeID
    select first;
Intersect可以被认为是Join的一种特殊情况,其中两个序列是相同类型的,因此可以应用于等式而无需为每种类型生成关键字进行比较。根据您的情况,Intersect不是一个选项。
如果您的第二个集合中存在重复的特定ID,并且您不希望在结果中重复该项,则可以使用GroupJoin而不是Join
var widgets1_in_widgets2 = from first in widgest1
    join second in widgets2
    on first.TypeID equals second.TypeID
    into matches
    where matches.Any()
    select first;

1
+1 "鉴于您的情况,Intersect 不是一个选项" 当自定义 IEqualityComparer<BaseWidget> 可能时,它将成为一个选项,因此需要一个公共基础类型。然后,您可以将其传递给 此重载,或者在 BaseWidget 中覆盖 Equals+GetHashCode 以默认按 TypeID 进行比较。 - Tim Schmelter

13

我喜欢这个解决方案,因为在代码中阅读起来很简单。

bool result = firstList.All(o => secondList.Any(w => w.Prop1 == o.Prop1 && w.Prop2 == o.Prop2));

请查看Fiddle示例的完整内容:Fiddle示例比较


1
谢谢,这对我有用。如果(projectIds.All(y => r.ProjectsId.Any(z => z.Equals(y)))) { reports.Add(r); } - Parveen
2
如果两个列表不同怎么办? - John Demetriou

5

使用Join方法存在这样的缺点,如果widgets1或widgets2包含多个相同TypeID的元素,则可能会导致结果重复(顺便说一下,这也适用于您的原始代码)。

以下代码将完全符合您的要求:返回所有在widgets2中具有相应TypeID的元素所对应的widgets1中的所有元素。

widgets1_in_widgets2 = (from w1 in widgets1
                        where widgets2.Any(w2 => w1.TypeID == w2.TypeID)
                        select w1).ToList()

1
虽然这样可以生成正确的输出,但性能会非常差,因为您正在对一个集合中的每个项执行线性搜索。 - Servy
“Join” 的缺点是,如果 widgets1 或 widgets2 包含多个具有相同 TypeID 的元素,则结果可能会重复。实际上,OP 中的代码与 “Join” 具有完全相同的输出,因此要么他在任一列表中没有重复键,要么他希望得到重复的结果。 - Servy
@Servy:好观点,我会在我的答案中提到。不过,变量命名让我认为这是无意的。 - Heinzi
如果您想解决的问题是这个问题,那么您可以简单地使用GroupJoin,它可以为您提供所描述的解决方案,而不需要您的答案具有相当高的性能成本。 还要注意,虽然我同意变量命名表明他不希望结果中有重复项,但他在第二个列表中可能没有重复项,因此无需将它们删除。 - Servy

0
您可以始终使用 Linq 的 .Except() 方法,为您执行比较操作,并返回“异常”或与您预期的相反的内容。
newErr = errList.Except(hList).ToList();

我在这里使用交集函数,将数组errList与另一个数组hList进行比较,并返回hList中不存在的行或其反转。

errList = "this is a new line"

hList =  ["first line","2nd line","3rd line"]

结果

newErr = "this is a new line"

0

尝试使用“Where”的重载

var isMatch = !widgets1.Where((w1, index) => w1.TypeId == widgets2[index].TypeId)).Any();

2
当问题已经被彻底回答过时,仅包含代码而没有解释的答案。 - ItamarG3
2
你可以更新这个答案来说明它的不同之处。我认为这是一个有用的解决方案,特别是在你想要比较顺序并避免任何线性搜索的情况下。 - DannyMeister

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接