Often times you need to be able to take action on an entire object graph, or specific types in a graph. An example of this could be walking the graph and setting a single property on all of your business objects. Perhaps a bool indicating the state of an object like IsDirty. Well since graphs are all different shapes a handy way to do this is through c# iterators. The idea being you supply a root object and the type of object you want to deal with. The method will then return to you each instance of said type.
There are a couple of things we want to do with this implementation.
- Ensure that it can be short circuited by the caller. After all you may want to use this method to inspect a graph for broken rules. And as soon as you find an InValid instance return and prevent further recursion.
- Supply a list of types to ignore in the recursion allowing the caller to limit useless graph traversal.
- Should be provided as an extension method so it can be used with all object graphs.
Lets take a look at how this might work.
public static class ObjectExtensions { /// <summary> /// Finds all object of the supplect type parameter. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="obj">The obj.</param> /// <param name="exclusionTypes">The exclusion types.</param> /// <returns></returns> public static IEnumerable<T> FindAll<T>(this object obj, params Type[] exclusionTypes) where T : class { if (obj is T) { yield return (obj as T); } var collection = obj as ICollection; if (collection != null) { foreach (var item in collection) { foreach (var child in FindAll<T>(item, exclusionTypes)) { yield return child; } } } else { foreach (var property in obj.GetType().GetProperties().Where(item => !IsTypeExcluded(item.PropertyType, exclusionTypes))) { var propertyValue = property.GetValue(obj, null); if (propertyValue != null) { foreach (var item in FindAll<T>(propertyValue, exclusionTypes)) { yield return item; } } } } } /// <summary> /// Determines whether [is type excluded] [the specified type]. /// </summary> /// <param name="type">The type.</param> /// <param name="exclusionTypes">The exclusion types.</param> /// <returns> /// <c>true</c> if [is type excluded] [the specified type]; otherwise, <c>false</c>. /// </returns> private static bool IsTypeExcluded(Type type, params Type[] exclusionTypes) { if (type == typeof(string) || type.IsValueType) { return true; } if (exclusionTypes != null && exclusionTypes.Length > 0) { if (exclusionTypes.Contains(type)) { return true; } } return false; } }
Lets look at its usage.
class Program { static void Main(string[] args) { var cat = new cat { Name = "Test", Arms = new List<Arm> { new Arm { FingerCount = 10 } }, Legs = new List<Leg> { new Leg { Size = 300 } } }; var x = cat.FindAll<Arm>().ToList(); Console.Read(); } } public class cat { public string Name { get; set; } public List<Leg> Legs { get; set; } public List<Arm> Arms { get; set; } } public class Leg { public int Size { get; set; } } public class Arm { public int FingerCount { get; set; } }
Enjoy!