Whats the 'modern' way to find common items in two Lists<T> of objects?

indra picture indra · Jul 26, 2011 · Viewed 9.8k times · Source

I have two Generic Lists containing different types, for the sake of example, lets call them Products and Employees. I'm trying to find Products that are based at the same location as Employees, i.e. where product.SiteId == emp.SiteId

List<Product> lstProds;
List<Employees> lstEmps;

My (old skool) brain is telling me to use a forEach loop to find the matches but I suspect there is a ('better'/terser/faster?) way to do it using Linq. Can anyone illuminate me? All the examples I've found online deal with Lists of primitives (strings/ints) and are not especially helpful.

Answer

Jon Skeet picture Jon Skeet · Jul 26, 2011

I would say:

var products = from product in lstProds
               join employee in lstEmps on product.SiteId equals employee.SiteId
               select product;

However, if there are multiple employees with the same site ID, you'll get the products multiple times. You could use Distinct to fix this, or build a set of site IDs:

var siteIds = new HashSet<int>(lstEmps.Select(emp => emp.SiteId));

var products = lstProds.Where(product => siteIds.Contains(product.SiteId));

That's assuming SiteId is an int - if it's an anonymous type or something similar, you may want an extra extension method:

public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source)
{
    return new HashSet<T>(source);
}

Then:

var siteIds = lstEmps.Select(emp => emp.SiteId).ToHashSet();
var products = lstProds.Where(product => siteIds.Contains(product.SiteId));

Alternatively, if you have few employees, this will work but is relatively slow:

var products = lstProds.Where(p => lstEmps.Any(emp => p.SiteId == emp.SiteId));

Add a ToList call to any of these approaches to get a List<Product> instead of an IEnumerable<Product>.