If I really had to stick with this layout for foo
, and I really had to make lookups as fast as possible (I don't care about memory size, and will be reusing the same objects repeatedly, so the cost of setting up a set of large structures in memory would be worth it), then I would do:
var byNameAndParentLookup = fooSource.ToLookup(f => Tuple.Create(f.parentid, f.name)); //will reuse this repeatedly
var results = byNameAndParentLookup[Tuple.Create(1, "bar2")].SelectMany(f => byNameAndParentLookup[Tuple.Create(f.id, "bar3")]);
That said, if I was going to store tree data in memory, I'd prefer to create a tree-structure, where each foo
had a children
collection (perhaps a dictionary keyed on name).
Edit: To explain a bit.
fooSource.ToLookup(f => Tuple.Create(f.parentid, f.name))
Goes through all the items in fooSource
(wherever our foo
objects are coming from), and for each one creates a tuple of the parentid
and the name
. This is used as a key for a lookup, so for each parentid-name combination we can retrieve 0 or more foo objects with that combo. (This will use the default string comparison, if you want something else such as case-insensitive, create an IEqualityComparer<Tuple<int, string>>
implementation that does the comparison you want and use .ToLookup(f => Tuple.Create(f.parentid, f.name), new MyTupleComparer())
).
The second line can be broken down into:
var partWayResults = byNameAndParentLookup[Tuple.Create(1, "bar2")];
var results = partWayResults.SelectMany(f => byNameAndParentLookup[Tuple.Create(f.id, "bar3")]);
The first line simply does a search on our lookup, so it returns an enumeration of those foo objects which have a parentid of 1 and a name of "bar2".
SelectMany
takes each item of an enumeration or queryable, and computes an expression that returns an enumeration, which is then flattened into a single enumeration.
In other words, it works a bit like this:
public static SelectMany<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> func)
{
foreach(TSource item in source)
foreach(TResult producedItem in func(item))
yield return producedItem;
}
In our case, the expression passed through takes the id of the element found in the first lookup, and then looks for any elements that have that as their parentid and have the name "bar2".
Hence, for every item with parentid 1 and name bar2, we find every item with that first item's id as its parentid and the name bar3. Which is what was wanted.