如何使用LINQ更好地ComponentTraversal.GetDescendants()
?
public static class ComponentTraversal
{
public static IEnumerable<Component> GetDescendants(this Composite composite)
{
//How can I do this better using LINQ?
IList<Component> descendants = new Component[]{};
foreach(var child in composite.Children)
{
descendants.Add(child);
if(child is Composite)
{
descendants.AddRange((child as Composite).GetDescendants());
}
}
return descendants;
}
}
public class Component
{
public string Name { get; set; }
}
public class Composite: Component
{
public IEnumerable<Component> Children { get; set; }
}
public class Leaf: Component
{
public object Value { get; set; }
}
我编辑了Chris的答案,提供了我已添加到Common库中的通用扩展方法。我可以看到这对其他人也有帮助,所以这里是:
public static IEnumerable<T> GetDescendants<T>(this T component, Func<T,bool> isComposite, Func<T,IEnumerable<T>> getCompositeChildren)
{
var children = getCompositeChildren(component);
return children
.Where(isComposite)
.SelectMany(x => x.GetDescendants(isComposite, getCompositeChildren))
.Concat(children);
}
谢谢克里斯!
请在http://blogs.msdn.com/b/wesdyer/archive/2007/03/23/all-about-iterators.aspx查看LukeH的答案。他的回答提供了更好的方法来解决这个问题,但我没有选择它,因为它不是我问题的直接答案。
答案 0 :(得分:7)
通常有充分的理由避免(1)递归方法调用,(2)嵌套迭代器和(3)很多一次性分配。这种方法避免了所有这些潜在的陷阱:
public static IEnumerable<Component> GetDescendants(this Composite composite)
{
var stack = new Stack<Component>();
do
{
if (composite != null)
{
// this will currently yield the children in reverse order
// use "composite.Children.Reverse()" to maintain original order
foreach (var child in composite.Children)
{
stack.Push(child);
}
}
if (stack.Count == 0)
break;
Component component = stack.Pop();
yield return component;
composite = component as Composite;
} while (true);
}
这是通用的等价物:
public static IEnumerable<T> GetDescendants<T>(this T component,
Func<T, bool> hasChildren, Func<T, IEnumerable<T>> getChildren)
{
var stack = new Stack<T>();
do
{
if (hasChildren(component))
{
// this will currently yield the children in reverse order
// use "composite.Children.Reverse()" to maintain original order
// or let the "getChildren" delegate handle the ordering
foreach (var child in getChildren(component))
{
stack.Push(child);
}
}
if (stack.Count == 0)
break;
component = stack.Pop();
yield return component;
} while (true);
}
答案 1 :(得分:3)
我不知道更好,但我认为这表现出相同的逻辑:
public static IEnumerable<Component> GetDescendants(this Composite composite)
{
return composite.Children
.Concat(composite.Children
.Where(x => x is Composite)
.SelectMany(x => x.GetDescendants())
);
}
可能会更短,但你所拥有的东西并没有错。正如我上面所说,这应该执行相同的事情,我怀疑功能的性能是否得到改善。
答案 2 :(得分:3)
var result = composite.Children.OfType<Composite>().SelectMany(child => child.GetDescendants()).Concat(composite.Children);
return result.ToList();
在执行从不完美语法到LINQ的转换时,通常很容易一次一步地进行转换。这是如何工作的:
答案 3 :(得分:1)
这是您可能想要实现迭代器的一个很好的示例。这具有以稍微更易读的语法进行延迟评估的优点。此外,如果您需要添加其他自定义逻辑,则此表单更具可扩展性
public static IEnumerable<Component> GetDescendants(this Composite composite)
{
foreach(var child in composite.Children)
{
yield return child;
if(!(child is Composite))
continue;
foreach (var subChild in ((Composite)child).GetDescendants())
yield return subChild;
}
}