【问题标题】:How to get all subobjects from a CustomObject with n Children/Subchildren and so on如何从具有 n 个子项/子子项等的 CustomObject 中获取所有子对象
【发布时间】:2012-01-18 13:08:25
【问题描述】:

我有一个带有 n 个孩子的 CustomObject。这些孩子是一个自定义对象列表。像这样的:

public class CustomObject
{
    public List<CustomObject> Children = new List<CustomObject>();
}

我正在寻找的是从 CustomObject 的单个实例中获取所有 n 个子项及其子项和子项等的最高效方法。有没有比循环遍历所有 veigns 直到我到达终点(null)更好的方法?

(C#,.NET 3.5)

为了更清楚,我将做一个示例结构:

//root object
CustomObject.Children ->
    CustomObject.Children ->
         CustomObject
         CustomObject
    CustomObject.Children ->
         CustomObject.Children ->
             CustomObject
         CustomObject
    CustomObject

在这种情况下,我需要获取根对象下的所有自定义对象。

【问题讨论】:

  • 我看不出这个实例的循环和递归有什么问题。可能有一些花哨的 LINQ 可以应用,有人可能会做出贡献,但除此之外,一个简单的循环和递归对我来说似乎非常合适。

标签: c# .net list children


【解决方案1】:

可能不会,但这完全取决于你是如何做到的。你可以递归地使用yield

public IEnumerable<CustomObject> AndChildren()
{
  yield return this;
  foreach(var child in Childs)
  {
    foreach(var nested in child.AndChildren())
    {
      yield return nested;
    }
  }
}

它的明显好处是它是后期绑定的,因此对您的内存消耗有更好的效果。缺点是它在对树中的任何节点进行任何修改方面都非常脆弱(如果任何列表被修改,foreach 迭代器将在 next 上引发异常)。

要解决这个问题,您可以使用 Linq .ToArray() 方法预先加载结果。

所以现在,如果你想遍历整个树,你只需这样做:

foreach(var obj in the_root_object.AndChildren())
{

}

假设the_root_objectCustomObject 的一个实例,如您所说,它具有各种“静脉”。

如果您对对象相对于其子对象的显示顺序有特定要求,则必须稍微重写。

【讨论】:

  • 谨防嵌套收益率,stackoverflow.com/questions/1043050/…
  • @Chris - 同意,如果我们有一个又大又深的树,那么可能会出现急切扁平化的问题,可能通过基于单栈的解决方案会好得多。
  • @Chris - 话虽如此,但我看不出这不如 Sebastian 发布的 Flatten 解决方案好 - 它们表面上是相同的(除了我没有写的事实它作为扩展,很容易完成)-如果您使用ToArray,我也会做同样的事情。任何递归解决方案都会有同样的问题,无论是否使用 yield。
  • 解决方案不是递归的,而是使用递归自动生成的枚举。 1) 保持状态和 2) 在每次迭代的每个可枚举项中下降都有很大的开销。树的迭代很容易变成 O(nlgn) 而不是 lg(n)。其他解决方案在递归时构建一个列表,交换 O(n) 空间的 O(n) 运行时间。自定义编写的迭代器将能够获得 O(n) 运行时间和 O(1) 空间,这可能是最好但最复杂的答案。
【解决方案2】:

不,您只需遍历所有内容。您可以使用递归方法:

public void GetItems(List<CustomObject> list) {
  list.Add(this);
  foreach (CustomObject child in Childs) {
    child.GetItems(list);
  }
}

用法:

List<CustomObject> items = new List<CustomObject>();
someCustomObject.GetItems(items);

注意:child的复数形式是children

【讨论】:

    【解决方案3】:

    我使用这个扩展方法:

    public static IEnumerable<T> Flatten<T>(this IEnumerable<T> hierarchy, Func<T, IEnumerable<T>> lambda)
        {
            var result = new List<T>();
    
            foreach (var item in hierarchy)
            {
                result.AddRange(Flatten(lambda(item), lambda));
                if (!result.Contains(item))
                    result.Add(item);
            }
    
            return result;
        }
    

    你会这样称呼它:

    MyObject.Childs.Flatten(c => c.Childs);
    

    这是一个相当丑陋的覆盖,如果需要,它会添加根对象:

    public static IEnumerable<T> Flatten<T>(this T @this, Func<T, IEnumerable<T>> lambda)
    {
        return new[] { @this }.Flatten(lambda);
    }
    

    【讨论】:

    • 我喜欢这个解决方案,但它应该可以在 T 上调用,添加 T,并且不需要检查每个项目是否已经包含。如果目标是每种类型只有一个实例,则应使用 Set。
    • 我想我有一个丑陋的覆盖来添加根 T,如果需要的话,它将它添加到一个列表中,然后在其中调用 flatten。关于集合的好点,我想它可以使用 HashSet 而不是列表
    【解决方案4】:

    我只会使用循环和递归,再简单不过了!

    private List<CustomObject> GetChildObjects(CustomObject)
    {
       List<CustomObject> retList = CustomObject.Childs;
    
       foreach(CustomerObject obj in retList)
       {
          retList.AddRange(GetChildObjects(obj));
       }
    
       return retList;
    }
    

    【讨论】:

    • 当被枚举的项目被改变时,这将给出一个例外
    【解决方案5】:

    您必须遍历每个 Childs 集合。但是,您可以考虑这样的事情:

    public class CustomObject
    {
        public List<CustomObject> Childs = new List<CustomObject>();
    
        protected IEnumerable<CustomObject> GetDecendants()
        {
            foreach (var child in Childs)
            {
                yield return child;
                foreach (var grandchild in child.GetDecendants())
                {
                    yield return grandchild;
                }
            }
        }
    }
    

    【讨论】:

      【解决方案6】:

      如果您对维护这些对象的层次结构不感兴趣,而只是在获取所有对象之后(如果您是的话,我无法从您的帖子中看出),您还可以考虑使用反射来获取所有对象输入自定义对象:

      List<CustomObject> Objects = new List<CustomObject>();
      
      Assembly asm = Assembly.LoadFrom(assemblyPath);
      Type[] types = asm.GetTypes();
      
      foreach (var t in types)
      {
          if (t.IsClass && t.IsSubclassOf(typeof(CustomObject)))
          {
              var instance = (CustomObject) Activator.CreateObject(t);
      
              if (!Objects.Contains(instance))
                  Objects.Add(instance);
          }
      }
      

      特别是在大型应用程序中,运行大量循环会对性能产生巨大影响,通常使用反射是一个不错的选择。

      【讨论】:

        猜你喜欢
        • 2011-09-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-21
        • 1970-01-01
        • 2014-02-11
        相关资源
        最近更新 更多