如果我真的必须坚持 foo 的这种布局,我真的必须尽可能快地进行查找(我不关心内存大小,并且会重复使用相同的对象,所以成本在内存中设置一组大型结构是值得的),那么我会这样做:
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")]);
也就是说,如果我要将树数据存储在内存中,我更愿意创建一个树结构,其中每个 foo 都有一个 children 集合(可能是一个以名称为键的字典)。
编辑:稍微解释一下。
fooSource.ToLookup(f => Tuple.Create(f.parentid, f.name))
遍历fooSource 中的所有项目(无论我们的foo 对象来自哪里),并为每个项目创建一个parentid 和name 的元组。这用作查找的键,因此对于每个 parentid-name 组合,我们可以使用该组合检索 0 个或多个 foo 对象。 (这将使用默认字符串比较,如果您想要其他内容(例如不区分大小写),请创建一个执行所需比较的 IEqualityComparer<Tuple<int, string>> 实现并使用 .ToLookup(f => Tuple.Create(f.parentid, f.name), new MyTupleComparer())。
第二行可以分解为:
var partWayResults = byNameAndParentLookup[Tuple.Create(1, "bar2")];
var results = partWayResults.SelectMany(f => byNameAndParentLookup[Tuple.Create(f.id, "bar3")]);
第一行只是简单地对我们的查找进行搜索,因此它返回父 ID 为 1 且名称为“bar2”的 foo 对象的枚举。
SelectMany 获取枚举或可查询的每个项目,并计算返回枚举的表达式,然后将其展平为单个枚举。
换句话说,它的工作方式有点像这样:
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;
}
在我们的例子中,传递的表达式采用在第一次查找中找到的元素的 id,然后查找任何以它为 parentid 并且名称为“bar2”的元素。
因此,对于父 ID 为 1 且名称为 bar2 的每个项目,我们会找到以第一个项目的 ID 作为其父 ID 和名称 bar3 的每个项目。这就是我们想要的。