When I have 2 List<string> objects, then I can use Intersect and Except on them directly to get an output IEnumerable<string>. That's simple enough, but what if I want the intersection/disjuction on something more complex?
Example, trying to get a collection of ClassA objects which is the result of the intersect on ClassA object's AStr1 and ClassB object's 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);
}
}
How can I fix the noted line to achieve this intersection.
MoreLINQ has ExceptBy. It doesn't have IntersectBy yet, but you could easily write your own implementation, and possibly even contribute it to MoreLINQ afterwards :)
It would probably look something like this (omitting error checking):
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;
}
}
}
If you wanted to perform an intersection on two completely different types which happened to have a common property type, you could make a more general method with three type parameters (one for first, one for second, and one for the common key type).
x ∈ A ∩ B if and only if x ∈ A and x ∈ B.
So, for each a in aObj, you can check if a.AStr1 is in the set of BStr values.
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));
}
this code:
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);
}
has passed the following test:
[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"));
}
来源:https://stackoverflow.com/questions/3998232/using-linq-to-objects-intersect-and-except-on-a-specific-property