diff --git a/src/ObjectTreeWalker/ObjectMemberIterator.cs b/src/ObjectTreeWalker/ObjectMemberIterator.cs index 9224e69..a0504f7 100644 --- a/src/ObjectTreeWalker/ObjectMemberIterator.cs +++ b/src/ObjectTreeWalker/ObjectMemberIterator.cs @@ -1,4 +1,6 @@ using System.Collections.Concurrent; +using System.Reflection; +using System.Runtime.CompilerServices; using Microsoft.Extensions.ObjectPool; namespace ObjectTreeWalker @@ -9,14 +11,74 @@ namespace ObjectTreeWalker public class ObjectMemberIterator { private static readonly ConcurrentDictionary ObjectAccessorCache = new(); - private static readonly ObjectPool>> TraversalQueuePool = - new DefaultObjectPoolProvider().Create>>(); + private static readonly ObjectPool> TraversalQueuePool = + new DefaultObjectPoolProvider().Create>(); - /// - /// Initializes a new instance of the class - /// - public ObjectMemberIterator() + public void Traverse(object obj, Action visitor) { + var objectGraph = ObjectEnumerator.Enumerate(obj.GetType()); + + var traversalQueue = TraversalQueuePool.Get(); + try + { + var rootObjectAccessor = GetCachedObjectAccessor(objectGraph.Type); + + foreach (var root in objectGraph.Roots) + { + traversalQueue.Enqueue( + (new IterationInfo(root.Name, obj, rootObjectAccessor), root)); + } + + while (traversalQueue.TryDequeue(out var current)) + { + var objectAccessor = GetCachedObjectAccessor(current.node.Type); + var nodeInstance = current.iterationItem.GetValue(); + + // we are only interested in iterating over the "data" vertices + // otherwise, we would get "foo(obj)" and then all foo's properties + if (current.node.Children.Count == 0) + { + visitor(current.iterationItem); + } + else + { + foreach (var child in current.node.Children) + { + traversalQueue.Enqueue( + (new IterationInfo(child.Name, nodeInstance!, objectAccessor), child)); + } + } + } + } + finally + { + TraversalQueuePool.Return(traversalQueue); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ObjectAccessor GetCachedObjectAccessor(Type type) => + ObjectAccessorCache.GetOrAdd(type, t => new ObjectAccessor(t)); + + public class IterationInfo + { + private readonly ObjectAccessor _objectAccessor; + private readonly object _object; + private readonly string _memberName; + + internal IterationInfo(string memberName, object obj, ObjectAccessor objectAccessor) + { + _memberName = memberName ?? throw new ArgumentNullException(nameof(memberName)); + _object = obj ?? throw new ArgumentNullException(nameof(obj)); + _objectAccessor = objectAccessor ?? throw new ArgumentNullException(nameof(objectAccessor)); + } + + public object? GetValue() => + !_objectAccessor.TryGetValue(_object, _memberName, out var value) ? + null : value; + + public void SetValue(object newValue) => + _objectAccessor.TrySetValue(_object, _memberName, newValue); } } }