在动态层次结构中按类型查找项目

时间:2015-08-24 16:49:50

标签: c# .net linq

我的一位同事问了一个有趣的问题。想象一下以下对象结构

项目 - > Sprint - >故事 - >任务

我的同事只对这些任务感兴趣。没有其他的。每个任务都有自己的ID,他想要选择项目的所有任务没有知道项目类型的确切层次结构。当某人更改层次结构中的某些内容(添加或删除某个级别)时,他现在还要修复查询以获取所有任务。

有没有很好的方法来实现这一目标?也许是这样的:

IEnumerable<Project> projects = GetProjectBy(...);
IEnumerable<Task> = projects.ItemsOfTypeInHierarchy<Task>();

没有反思可能吗?

(我知道它如何与.SelectMany一起工作并了解确切的结构)

2 个答案:

答案 0 :(得分:0)

模型的每个部分是否通过一些公共接口或基类公开其子级?如果他们实现了像

那样简单的共同点
public interface IHierarchicalParent
{
    IEnumerable<IHierarchicalParent> Children { get; }
}

你可以在没有反射的情况下递归搜索树。

如果你不能修改每个类,你可以为每种类型编写一个适配器 - 尽管那时你必须编写一堆包装器。根据您的使用案例(isas,巧妙使用dynamic或其他内容)灵活选择要制作的包装器需要进行一些类型检查,但不会反映成员。

您还可以将适配器“展平”为单个方法,该方法接受基本实体对象并返回相同类型的枚举。

但是,如果您可以在编译时确定特定的层次结构,则可以避免反映类型。您可以通过ProjectAdapter为其SprintAdapter中的每个Children返回Sprint作为Project的{​​{1}}的可枚举来执行此操作。这当然使它不灵活,但根本没有类型检查!然后,只需将所有对象视为IHierarchicalParent,并以相同的方式进行递归检查。

(从我的评论中移开了这个答案并对其进行了扩展。)

答案 1 :(得分:0)

这个答案的灵感来自Miguel Castro(一个复数作者),它确实使用了反射,但我已经使用了它并且它表现得很好。想象一下,由于它的多功能性,我分享了它。你可以做的不仅仅是获取物品,你可以随心所欲地做任何事情!

创建类似于ObjectBase的东西,并且需要在层次结构中找到的任何类都继承自该基类。我真的从我的源代码控制中复制了这个并为这篇文章做了一些调整,但是这个ObjectBase的目标是让所有的&#34;脏&#34;树中的物体由&#34;行走&#34;那个树。您可以传递任何您想要的匿名方法,匿名方法是您实际想要的对象(将其添加到集合,执行方法,更改属性等)

public abstract class ObjectBase
    {
        //The IsDirty Property
        protected bool _isDirty;
        public bool IsDirty
        {
            get { return _isDirty; }
            set { _isDirty = value; }
        }

        /// <summary>
        /// Using this item as the root, walk the tree to get all dirty objects
        /// </summary>
        /// <returns>IEnumerable<ObjectBase></returns>
        public IEnumerable<ObjectBase> GetDirtyObjects()
        {
            var dirtyObjects = new List<ObjectBase>();
            WalkTree(
            o =>
            {
                if (o.IsDirty)
                    dirtyObjects.Add(o);

                return false; // can return true to stop walk on first object
            });

            return dirtyObjects;
        }

        ///Sets all DirtyObjects in the tree to clean
        public void CleanTree()
        {
            WalkTree(
            o =>
            {
                o.IsDirty = false;
                return false; // can return true to stop walk on first object
            });

        }

        //Accepts a function to be executed, and any properties that you might want to be excluded
        //in this instance the function we'll be using is getting the dirty objects(above)
        public void WalkTree(Func<ObjectBase, bool> walkSnippet, IEnumerable<string> exemptProperties = null)
        {
            //Create a list of visited objects to prevent looking at the same objects twice
            List<ObjectBase> visited = new List<ObjectBase>();

            //Take into consideration any exemptions via property name
            List<string> exemptions = new List<string>();
            if (exemptProperties != null)
                exemptions = exemptProperties.ToList();

            Walk(this, visited, walkSnippet, exemptions);
        }

        //Does the actual walking of the tree, looking through all the properties
        //on the class, checking to see if that property is of the type, or an 
        //IEnumerable of ObjectBase
        private void Walk(ObjectBase obj, List<ObjectBase> visited, Func<ObjectBase, bool> walkSnippet, IEnumerable<string> exemptions)
        {
            //if you have already checked this object, check it
            if (obj != null && !visited.Contains(obj))
            {
                //add to the visited list
                visited.Add(obj);
                //invoke the walk snippet declared in GetDirtyObjects
                walkSnippet.Invoke(obj);

                //Loop through all the properties that implement ObjectBase
                //or are an IEnumerable<ObjectBase>, that aren't exempt
                foreach (var property in ReflectionHelper.GetTreeProperties(obj, true).Where(x => !exemptions.Contains(x.Name)))
                {
                    //If it's a single object, recurse this method
                    if (property.PropertyType.IsSubclassOf(typeof(ObjectBase)))
                    {
                        Walk((ObjectBase)property.GetValue(obj), visited, walkSnippet, exemptions);
                    }
                    else
                    {
                        //if it's an IEnumerable property, recurse this method
                        //over the collection
                        var objEnumerable = (IEnumerable<ObjectBase>)property.GetValue(obj);
                        if (objEnumerable != null)
                        {
                            foreach (var nestedObj in objEnumerable)
                            {
                                Walk((ObjectBase)nestedObj, visited, walkSnippet, exemptions);
                            }
                        }
                    }
                }
            }
        }
    }

这是ReflectionHelper:

public static class ReflectionHelper
{
    public static IEnumerable<PropertyInfo> GetTreeProperties<T>(T obj)
    {
        var type = obj.GetType();
        var properties = type.GetProperties()
           .Where(x => x.PropertyType.IsSubclassOf(typeof(T)) || x.GetValue(obj) as IEnumerable<T> != null);

        return properties;
    }
}