Использование LINQ для объектов Intersect и Except для определенного свойства

Когда у меня есть 2 объекта List<string>, я могу напрямую использовать на них Intersect и Except, чтобы получить результат IEnumerable<string>. Это достаточно просто, но что, если я хочу пересечение/разъединение чего-то более сложного?

Пример: попытка получить набор объектов ClassA, который является результатом пересечения AStr1 объекта ClassA и BStr объекта ClassB; :

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

Как я могу исправить отмеченную линию для достижения этого пересечения.


person Diana    schedule 22.10.2010    source источник


Ответы (3)


MoreLINQ имеет ExceptBy. У него еще нет 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;
        }
    }
}

Если вы хотите выполнить пересечение двух совершенно разных типов, которые имеют общий тип свойства, вы можете создать более общий метод с тремя параметрами типа (один для first, один для second и один для общего типа ключа).

person Jon Skeet    schedule 22.10.2010
comment
Привет @Jon Skeet, я не понимаю, как передать четвертый параметр. Я определяю класс, реализуя IEqualityComparer, а затем передаю экземпляр этого класса? - person corei11; 28.11.2016
comment
@corei11: Да, если вам нужно что-то нестандартное, или используйте EqualityComparer<T>.Default, если вас устраивает операция равенства по умолчанию. - person Jon Skeet; 28.11.2016
comment
Спасибо @Джон Скит. Здесь у меня есть два IEnumerable owner1 и owner2. И я определяю класс с именем Compar, реализуя IEqualityComparer. Теперь вызовите метод таким образом owner1 = owner1.IntersectBy(owner2, item => item.Email_Address, new Compar<string>()). На самом деле я хочу прояснить, как передать мой класс Compar. То есть напрямую я пишу строку или есть другой лучший способ, или мой подход в порядке? Мой прототип класса Compar — public class Compar<Tkey> : IEqualityComparer<Tkey>. Извиняюсь. Это может быть глупый вопрос. Спасибо. - person corei11; 28.11.2016
comment
@corei11: На этом этапе я думаю, вам следует задать новый вопрос с минимально воспроизводимым примером. - person Jon Skeet; 28.11.2016

x ∈ A ∩ B тогда и только тогда, когда x ∈ A и x ∈ B.

Итак, для каждого a в aObj вы можете проверить, входит ли 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));
}
person dtb    schedule 22.10.2010

этот код:

    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"));
    }
person AntonioR    schedule 26.10.2010