在动态层次结构中按类型查找项
本文关键字:类型 查找 动态 层次结构 | 更新日期: 2023-09-27 18:09:39
我的一个同事问了一个有趣的问题。想象一下下面的对象结构
项目-> sprint ->故事->任务
我的同事只是对任务感兴趣。什么都没有。每个任务都有自己的id,他想在不知道项目类型的确切层次结构的情况下选择项目的所有任务。当有人在层次结构中更改某些内容(添加或删除级别)时,他现在还必须修复查询以获得所有任务。
是否有一个好的方法来实现这一点?也许像这样:
IEnumerable<Project> projects = GetProjectBy(...);
IEnumerable<Task> = projects.ItemsOfTypeInHierarchy<Task>();
不反射是可能的吗?
(我知道它是如何工作的.SelectMany
和知道确切的结构)
模型的每个部分是否通过一些公共接口或基类公开其子部分?如果它们实现了一些共同的东西,比如
public interface IHierarchicalParent
{
IEnumerable<IHierarchicalParent> Children { get; }
}
你可以递归地搜索树而不需要反射。
如果你不能修改每个类,你可以为每个类型写一个适配器——尽管这样你必须写一堆包装器。根据用例(is
, as
, dynamic
的巧妙使用,或其他)灵活选择使用哪个包装器涉及一些类型检查,但不反映到成员中。
您还可以将适配器"扁平化"为单个方法,该方法接受基本实体对象并返回相同类型的可枚举对象。
但是,如果您可以在编译时确定特定的层次结构,则可以完全避免反映类型。您可以通过使ProjectAdapter
返回SprintAdapter
的一个可枚举数作为Children
,对于Project
中的每个Sprint
。这当然使它不灵活,但根本没有类型检查!之后,只需将所有对象视为IHierarchicalParent
,并以相同的方式进行递归检查。
(从我的评论中移出这个答案并对其进行扩展)
这个答案的灵感来自Miguel Castro (pluralsight的作者),它确实使用了反射,但我已经使用了它,它的性能很好。我想,由于它的多功能性,我应该分享它。你可以做的不仅仅是获得对象,你可以用它们做任何你想做的事情!
创建类似于ObjectBase的东西,并且需要在层次结构中找到的任何类都继承自该基类。我从我的源代码控制中复制了这一点,并在本文中做了一些调整,但这个ObjectBase的目标是通过"遍历"树来获得树中的所有"脏"对象。你可以传递任何你想要的匿名方法,匿名方法就是你真正想要的对象(将其添加到集合中,执行方法,更改属性,等等)
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;
}
}