使用LINQ对对象的特定属性进行Intersect和Except操作

6
当我有两个 List<string> 对象时,我可以直接使用 IntersectExcept 来获取一个 IEnumerable<string> 类型的输出。这很简单,但是如果我想在更复杂的内容上进行交集/差集操作呢?
例如,尝试获取一个 ClassA 对象集合,该集合是由 ClassA 对象的 AStr1ClassB 对象的 BStr 进行交集操作后得到的:
public class ClassA {
    public string AStr1 { get; set; }
    public string AStr2 { get; set; }
    public int AInt { get; set; }
}
public class ClassB {
    public string BStr { get; set; }
    public int BInt { get; set; }
}
public class Whatever {
    public void xyz(List<ClassA> aObj, List<ClassB> bObj) {
        // *** this line is horribly incorrect ***
        IEnumberable<ClassA> result =
            aObj.Intersect(bObj).Where(a, b => a.AStr1 == b.BStr);
    }
}

如何修复这个问题,以实现这个交叉点。
3个回答

14

MoreLINQExceptBy方法。目前还没有IntersectBy方法,但是你可以轻松地编写自己的实现,并可能在之后将其贡献给MoreLINQ :)

它可能会看起来像这样(省略错误检查):

public static IEnumerable<TSource> IntersectBy<TSource, TKey>(
    this IEnumerable<TSource> first,
    IEnumerable<TSource> second,
    Func<TSource, TKey> keySelector,
    IEqualityComparer<TKey> keyComparer)
{
    HashSet<TKey> keys = new HashSet<TKey>(first.Select(keySelector),
                                           keyComparer);
    foreach (var element in second)
    {
        TKey key = keySelector(element);
        // Remove the key so we only yield once
        if (keys.Remove(key))
        {
            yield return element;
        }
    }
}

如果您想对两个完全不同的类型执行交集操作,这两个类型恰好有一个共同的属性类型,那么您可以使用三个类型参数(分别为firstsecond和共同键类型)来创建一个更通用的方法。

嗨@Jon Skeet,我不明白如何传递第四个参数。 我需要通过实现IEqualityComparer接口来定义一个类,然后传递该类的实例吗? - corei11
@corei11:是的,如果你需要定制化的东西 - 或者如果你对默认的相等操作感到满意,可以使用EqualityComparer<T>.Default - Jon Skeet
感谢@Jon Skeet。这里我有两个IEnumerable owner1owner2。我通过实现IEqualityComparer定义了一个名为Compar的类。现在通过这种方式调用方法owner1 = owner1.IntersectBy(owner2, item => item.Email_Address, new Compar<string>())。实际上,我想澄清如何传递我的Compar类。是直接写字符串还是有其他更好的方法,或者我的方法可以吗?我的Compar类原型是public class Compar<Tkey> : IEqualityComparer<Tkey>。抱歉,这可能是一个愚蠢的问题。谢谢。 - corei11
@corei11:我认为你现在应该提出一个带有 [mcve] 的新问题。 - Jon Skeet

3

如果且仅当x属于A并且x属于B时,x ∈ A ∩ B。

因此,对于aObj中的每个a,您可以检查a.AStr1是否在BStr值的集合中。

public void xyz(List<ClassA> aObj, List<ClassB> bObj)
{
    HashSet<string> bstr = new HashSet<string>(bObj.Select(b => b.BStr));
    IEnumerable<ClassA> result = aObj.Where(a => bstr.Contains(a.AStr1));
}

1

这段代码:

    public IEnumerable<ClassA> xyz(List<ClassA> aObj, List<ClassB> bObj)
    {
        IEnumerable<string> bStrs = bObj.Select(b => b.BStr).Distinct();
        return aObj.Join(bStrs, a => a.AStr1, b => b, (a, b) => a);
    }

已通过以下测试:

    [TestMethod]
    public void PropertyIntersectionBasedJoin()
    {
        List<ClassA> aObj = new List<ClassA>()
                                {
                                    new ClassA() { AStr1 = "a" }, 
                                    new ClassA() { AStr1 = "b" }, 
                                    new ClassA() { AStr1 = "c" }
                                };
        List<ClassB> bObj = new List<ClassB>()
                                {
                                    new ClassB() { BStr = "b" }, 
                                    new ClassB() { BStr = "b" }, 
                                    new ClassB() { BStr = "c" }, 
                                    new ClassB() { BStr = "d" }
                                };

        var result = xyz(aObj, bObj);

        Assert.AreEqual(2, result.Count());
        Assert.IsFalse(result.Any(a => a.AStr1 == "a"));
        Assert.IsTrue(result.Any(a => a.AStr1 == "b"));
        Assert.IsTrue(result.Any(a => a.AStr1 == "c"));
    }

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