Walk Object Graph
public static class ReflectionExtensions
{
/// <summary>
/// Walks an object graph performing a function for any object in the graph that implements T or any collection that implements T
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="toWalk">Item to walk from</param>
/// <param name="snippetForObject">Function for each item of type T</param>
/// <param name="snippetForCollection">Function for each IEnumerable of type T</param>
/// <param name="exemptProperties">Properties to exempt</param>
public static void WalkObjectGraph<T>(this T toWalk, Func<T, bool> snippetForObject,
Action<IList> snippetForCollection = null,
params string[] exemptProperties) where T : class
{
var visited = new List<T>();
Action<T> walk = null;
var exemptions = new List<string>();
if (exemptProperties != null)
exemptions = exemptProperties.ToList();
walk = o =>
{
if (o != null && !visited.Contains(o))
{
visited.Add(o);
var exitWalk = snippetForObject.Invoke(o);
if (!exitWalk)
{
var properties = o.GetBrowsableProperties();
foreach (var property in properties)
{
if (!exemptions.Contains(property.Name))
{
var propertyType = property.PropertyType;
if (typeof(T).IsAssignableFrom(propertyType))
{
var subProperty = (T)property.GetValue(o, null);
walk(subProperty);
}
var implementsIEnumerable = propertyType.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>));
if (implementsIEnumerable)
{
var genericArgument = propertyType.GetGenericArguments()[0];
if (typeof(T).IsAssignableFrom(genericArgument))
{
var col = property.GetValue(o, null) as IList;
if (col != null)
{
if (snippetForCollection != null)
snippetForCollection.Invoke(col);
foreach (var item in col)
{
walk((T)item);
}
}
}
}
}
}
}
}
};
walk(toWalk);
}
/// <summary>
/// Get all public reference properties or collection types (implement IEnumerable) which are not decorated with IEnumerable
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public static IEnumerable<PropertyInfo> GetBrowsableProperties(this object item)
{
return item.GetType()
.GetProperties()
.Where(info => !Attribute.IsDefined(info, typeof(NoneNavigateable))
&& (info.PropertyType.IsClass || typeof(IEnumerable).IsAssignableFrom(info.PropertyType))
&& info.PropertyType != typeof(string));
}
}
/// <summary>
/// A decoration only property used to determine if a property should be walked by specific graph walking functions
/// </summary>
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
public class NoneNavigateableAttribute : Attribute{}