我的一位同事问了一个有趣的问题。想象一下以下对象结构
项目 - > Sprint - >故事 - >任务
我的同事只对这些任务感兴趣。没有其他的。每个任务都有自己的ID,他想要选择项目的所有任务没有知道项目类型的确切层次结构。当某人更改层次结构中的某些内容(添加或删除某个级别)时,他现在还要修复查询以获取所有任务。
有没有很好的方法来实现这一目标?也许是这样的:
IEnumerable<Project> projects = GetProjectBy(...);
IEnumerable<Task> = projects.ItemsOfTypeInHierarchy<Task>();
没有反思可能吗?
(我知道它如何与.SelectMany
一起工作并了解确切的结构)
答案 0 :(得分:0)
模型的每个部分是否通过一些公共接口或基类公开其子级?如果他们实现了像
那样简单的共同点public interface IHierarchicalParent
{
IEnumerable<IHierarchicalParent> Children { get; }
}
你可以在没有反射的情况下递归搜索树。
如果你不能修改每个类,你可以为每种类型编写一个适配器 - 尽管那时你必须编写一堆包装器。根据您的使用案例(is
,as
,巧妙使用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;
}
}