Linq扩展方法,如何在集合中查找子递归

我已经熟悉了Linq,但对扩展方法了解甚少,我希望有人可以帮助我。

所以我有这个分层集合伪代码,即:

class Product prop name prop type prop id prop List children 

我有一个产品列表产品列表。

有没有什么办法可以通过id使用扩展方法在这个集合中查找产品? 换句话说,我需要在层次结构中的某个位置使用一个项目。

这是一个通用的解决方案,一旦找到匹配,就会短路层次结构的遍历。

 public static class MyExtensions { public static T FirstOrDefaultFromMany( this IEnumerable source, Func> childrenSelector, Predicate condition) { // return default if no items if(source == null || !source.Any()) return default(T); // return result if found and stop traversing hierarchy var attempt = source.FirstOrDefault(t => condition(t)); if(!Equals(attempt,default(T))) return attempt; // recursively call this function on lower levels of the // hierarchy until a match is found or the hierarchy is exhausted return source.SelectMany(childrenSelector) .FirstOrDefaultFromMany(childrenSelector, condition); } } 

要在你的情况下使用它:

 var matchingProduct = products.FirstOrDefaultFromMany(p => p.children, p => p.Id == 27); 

您可以使用此扩展方法展平树结构:

 static IEnumerable Flatten(this IEnumerable source) { return source.Concat(source.SelectMany(p => p.Children.Flatten())); } 

用法:

 var product42 = products.Flatten().Single(p => p.Id == 42); 

请注意,这可能不是很快。 如果您反复需要按ID查找产品,请创建字典:

 var dict = products.Flatten().ToDictionary(p => p.Id); var product42 = dict[42]; 

我只是重构dtb的解决方案,使其更通用。 试试这个扩展方法:

 public static IEnumerable Flatten(this IEnumerable source, Func recursion) where R : IEnumerable { return source.SelectMany(x => (recursion(x) != null && recursion(x).Any()) ? recursion(x).Flatten(recursion) : null) .Where(x => x != null); } 

你可以像这样使用它:

 productList.Flatten(x => x.Children).Where(x => x.ID == id); 
 static IEnumerable FindProductById(this IEnumerable source, int id) { return source.FirstOrDefault(product => product.Id = id) ?? source.SelectMany(product => product.Children).FindProductById(id); } 

使用yield来优化所需枚举的替代解决方案。

 public static IEnumerable SelectManyRecursive( this IEnumerable source, Func> childrenSelector) { if (source == null) throw new ArgumentNullException("source"); foreach (var i in source) { yield return i; var children = childrenSelector(i); if (children != null) { foreach (var child in SelectManyRecursive(children, childrenSelector)) { yield return child; } } } } 

然后你可以通过调用类似FirstOrDefault的东西来找到匹配:

  var match = People.SelectManyRecursive(c => c.Children) .FirstOrDefault(x => x.Id == 5); 

如果要“子迭代”并在产品列表中查找子项:

 List Product Child Child Child Child Product Child Child *find this one Child 

您可以使用现有的SelectMany扩展方法。 SelectMany可用于“展平”两级层次结构。

以下是SelectMany的一个很好的解释: http : //team.interknowlogy.com/blogs/danhanan/archive/2008/10/10/use-linq-s-selectmany-method-to-quot-flatten-quot-collections.aspx

你的语法是这样的:

 List p = GetProducts(); //Get a list of products var child = from c in p.SelectMany(p => p.Children).Where(c => c.Id == yourID);