/// <summary> /// Traverse the given instance /// </summary> /// <param name="instance">The object instance you want to traverse</param> /// <param name="maxDepth">The max depth you find appropriate</param> /// <param name="listener">The listener that will be notified when we find something</param> public virtual void TraverseInstance(object instance, int maxDepth, IInstanceListener listener) { if (!ShouldRecurse(instance.GetType())) { throw new InstanceTraversalException($"{instance.GetType()} is not a type that can be traversed."); } var context = new InstanceTraversalContext { Instance = instance, MaxDepth = maxDepth }; // // constructors // we are doing constructors in the non-recursive part // as we are not interested in constructors of field // and property types // var constructors = instance.GetType().GetTypeInfo().GetConstructors(); foreach (var constructorInfo in constructors) { listener.OnConstructor(constructorInfo, context); } Worker(instance, listener, context); }
/// <summary> /// Call OnField or OnProperty for the given member /// </summary> private void CallListener(IInstanceListener listener, InstanceTraversalContext context, MemberInfo memberInfo, Func <object> valueGetter) { if (memberInfo.MemberType == MemberTypes.Field) { listener.OnField((FieldInfo)memberInfo, valueGetter, context); } else { listener.OnProperty((PropertyInfo)memberInfo, valueGetter, context); } }
private void Worker(object instance, IInstanceListener listener, InstanceTraversalContext context) { var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; var typeInfo = instance.GetType().GetTypeInfo(); // // fields // var fields = typeInfo.GetFields(bindingFlags); foreach (var fieldInfo in fields) { if (!IsBackingField(fieldInfo)) { FieldAndPropertyHandler(listener, fieldInfo, context, instance); } } // // properties // var props = typeInfo.GetProperties(bindingFlags); foreach (var propertyInfo in props) { if (!IsIndexProperty(propertyInfo)) { FieldAndPropertyHandler(listener, propertyInfo, context, instance); } } // // methods // var methods = typeInfo.GetMethods(bindingFlags); foreach (var methodInfo in methods) { if ( methodInfo.DeclaringType != typeof(System.Object) && // do not "announce" methods like Object.ToString !IsBackingMethod(methodInfo) // do not "announce" auto property backing methods ) { listener.OnMethod(methodInfo, context); } } }
/// <summary> /// Traverse the given instance /// </summary> /// <param name="instance">The object instance you want to traverse</param> /// <param name="maxDepth">The max depth you find appropriate</param> /// <param name="listener">The listener that will be notified when we find something</param> public virtual void TraverseInstance(object instance, int maxDepth, IInstanceListener listener) { var context = new InstanceTraversalContext { Instance = instance, MaxDepth = maxDepth }; // // constructors // we are doing constructors in the non-recursive part // as we are not interested in constructors of field // and property types // var constructors = instance.GetType().GetTypeInfo().GetConstructors(); foreach (var constructorInfo in constructors) { listener.OnConstructor(constructorInfo, context); } Worker(instance, listener, context); }
/// <summary> /// Field and properties are actually quite similar /// in this context. This method contains "generic" handling /// of both types, so we avoid duplication. /// </summary> /// <exception cref="InvalidOperationException">Thrown if we somehow try to iterate something that is not an IEnumerable</exception> private void FieldAndPropertyHandler(IInstanceListener listener, MemberInfo memberInfo, InstanceTraversalContext context, object instance) { Func <object> valueGetter = () => memberInfo.GetValue(instance); CallListener(listener, context, memberInfo, valueGetter); var valueType = memberInfo.GetTypeOfValue(); if (ShouldIterate(valueType)) { object value = valueGetter.Invoke(); if (value != null) { if (value is IDictionary) { var dictionary = value as IDictionary; foreach (DictionaryEntry entry in dictionary) { context.BreadcrumbStack.Push($"{memberInfo.Name}[{entry.Key}]"); CallListener(listener, context, memberInfo, () => entry.Value); if (ShouldRecurse(entry.Value.GetType())) { if (context.CanGoDeeper()) { Worker(entry.Value, listener, context); } else { listener.OnMaxDepthReached(context); } } context.BreadcrumbStack.Pop(); } } else { var enumerable = value as IEnumerable; if (enumerable == null) { throw new InvalidOperationException($"Trying to iterate over {context.BreadcrumbAsString}.{memberInfo.Name}, but could not cast to IEnumerable."); } var index = 0; foreach (var element in enumerable) { // If TraverseInstance is called directly on a list with capacity greater than the size // Then null elements will be present if (element == null) { continue; } context.BreadcrumbStack.Push($"{memberInfo.Name}[{index}]"); CallListener(listener, context, memberInfo, () => element); if (ShouldRecurse(element.GetType())) { if (context.CanGoDeeper()) { Worker(element, listener, context); } else { listener.OnMaxDepthReached(context); } } context.BreadcrumbStack.Pop(); index++; } } } } else if (ShouldRecurse(valueType)) { // // recursion // if (context.CanGoDeeper()) { object value = valueGetter.Invoke(); if (value != null) { context.BreadcrumbStack.Push(memberInfo.Name); Worker(value, listener, context); context.BreadcrumbStack.Pop(); } } else { listener.OnMaxDepthReached(context); } } }
/// <summary> /// Call OnField or OnProperty for the given member /// </summary> private static IInstanceListenerOnFieldOrPropResult CallListener(IInstanceListener listener, InstanceTraversalContext context, MemberInfo memberInfo, Func <object> valueGetter) { if (memberInfo.MemberType == MemberTypes.Field) { return(listener.OnField((FieldInfo)memberInfo, valueGetter, context)); } return(listener.OnProperty((PropertyInfo)memberInfo, valueGetter, context)); }