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

C# 在动态层次结构中按类型查找项目,c#,.net,linq,C#,.net,Linq,我的一位同事问了一个有趣的问题。想象一下下面的对象结构 项目-->短跑-->故事-->任务 我的同事只是对这些任务感兴趣。没有别的了。每个任务都有自己的id,他希望选择项目的所有任务,而不知道类型项目的确切层次结构。当某人更改层次结构中的某些内容(添加或删除一个级别)时,他现在还必须修复查询以获取所有任务 有没有一个好的方法来实现这一点?也许是这样的: IEnumerable<Project> projects = GetProjectBy(...); IEnumerable<

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

项目-->短跑-->故事-->任务

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

有没有一个好的方法来实现这一点?也许是这样的:

IEnumerable<Project> projects = GetProjectBy(...);
IEnumerable<Task> = projects.ItemsOfTypeInHierarchy<Task>();
IEnumerable projects=GetProjectBy(…);
IEnumerable=projects.ItemsOfTypeInHierarchy();
不经思考就可以吗


(我知道它是如何与
一起工作的。选择many
并知道确切的结构)

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

public interface IHierarchicalParent
{
    IEnumerable<IHierarchicalParent> Children { get; }
}
公共接口IHierarchicalParent
{
IEnumerable子项{get;}
}
可以递归地搜索树而不进行反射

如果不能修改每个类,那么可以为每个类型编写一个适配器——尽管之后必须编写一系列包装器。根据您的用例灵活地选择要制作的包装涉及一些类型检查(
作为
,巧妙地使用
动态
,等等),但不会反映到成员中

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

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


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

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

public interface IHierarchicalParent
{
    IEnumerable<IHierarchicalParent> Children { get; }
}
公共接口IHierarchicalParent
{
IEnumerable子项{get;}
}
可以递归地搜索树而不进行反射

如果不能修改每个类,那么可以为每个类型编写一个适配器——尽管之后必须编写一系列包装器。根据您的用例灵活地选择要制作的包装涉及一些类型检查(
作为
,巧妙地使用
动态
,等等),但不会反映到成员中

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

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


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

这个答案的灵感来源于米格尔·卡斯特罗(pluralsight的作者),它确实使用了反射,但我使用了它,它表现得很好。我想,我会分享它的多功能性。你可以做的不仅仅是获取对象,你可以用它们做任何你想做的事情

创建类似于ObjectBase的东西,需要在层次结构中找到的任何类都继承自该基类。我从我的源代码管理中复制了这一点,并在这篇文章中做了一些调整,但是这个ObjectBase的目标是通过“遍历”树来获取树中的所有“脏”对象。您可以传递任何您想要的匿名方法,该匿名方法就是您实际想要传递给该对象的方法(将其添加到集合、执行方法、更改属性等)

公共抽象类ObjectBase
{
//IsDirty属性
受保护的财产;
公共图书馆
{
获取{return\u isDirty;}
设置{u isDirty=value;}
}
/// 
///使用此项作为根,遍历树以获取所有脏对象
/// 
///数不清
公共IEnumerable GetDirtyObjects()
{
var dirtyObjects=新列表();
步行树(
o=>
{
如果(o.IsDirty)
dirtyObjects.Add(o);
return false;//可以返回true以停止在第一个对象上行走
});
返回脏物体;
}
///将树中的所有DirtyObjects设置为“清理”
公共树()
{
步行树(
o=>
{
o、 IsDirty=false;
return false;//可以返回true以停止在第一个对象上行走
});
}
//接受要执行的函数以及可能要排除的任何属性
//在本例中,我们将使用的函数是获取脏对象(上图)
public void WalkTree(Func walkSnippet,IEnumerable exemptProperties=null)
{
//创建已访问对象的列表,以防止查看同一对象两次
访问列表=新列表();
//通过财产名称考虑任何豁免
列表豁免=新列表();
if(exemptProperties!=null)
豁免=适当的豁免
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;
    }
}