Skip to content

Commit

Permalink
feat: implement basic object member iterator functionality, probably …
Browse files Browse the repository at this point in the history
…will need more testing
  • Loading branch information
myarichuk committed Sep 17, 2022
1 parent ee10271 commit 219854d
Showing 1 changed file with 68 additions and 6 deletions.
74 changes: 68 additions & 6 deletions 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
Expand All @@ -9,14 +11,74 @@ namespace ObjectTreeWalker
public class ObjectMemberIterator
{
private static readonly ConcurrentDictionary<Type, ObjectAccessor> ObjectAccessorCache = new();
private static readonly ObjectPool<Queue<KeyValuePair<string, object>>> TraversalQueuePool =
new DefaultObjectPoolProvider().Create<Queue<KeyValuePair<string, object>>>();
private static readonly ObjectPool<Queue<(IterationInfo iterationItem, ObjectGraphNode node)>> TraversalQueuePool =
new DefaultObjectPoolProvider().Create<Queue<(IterationInfo iterationItem, ObjectGraphNode node)>>();

/// <summary>
/// Initializes a new instance of the <see cref="ObjectMemberIterator"/> class
/// </summary>
public ObjectMemberIterator()
public void Traverse(object obj, Action<IterationInfo> 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);
}
}
}

0 comments on commit 219854d

Please sign in to comment.