Circular References

Dumpify safely handles objects with circular references, preventing infinite loops and stack overflows during rendering.

The Problem

Consider a linked list or parent-child relationship where objects reference each other:

public class Node
{
    public int Value { get; set; }
    public Node? Next { get; set; }
}

var node1 = new Node { Value = 1 };
var node2 = new Node { Value = 2 };
node1.Next = node2;
node2.Next = node1;  // Circular reference!

Without circular reference handling, attempting to serialize or display this object would cause an infinite loop.

How Dumpify Handles It

Dumpify tracks visited objects during rendering. When it encounters an object it has already seen, it displays a reference indicator instead of recursing infinitely.

var node = new Node { Value = 1 };
node.Next = node;  // Self-reference

node.Dump();

Output shows the circular reference is detected and safely handled.

Common Circular Reference Scenarios

Self-Reference

public class Category
{
    public string Name { get; set; }
    public Category? Parent { get; set; }
}

var root = new Category { Name = "Root" };
root.Parent = root;  // Points to itself

root.Dump();  // Safe!

Parent-Child Relationships

public class TreeNode
{
    public string Name { get; set; }
    public TreeNode? Parent { get; set; }
    public List<TreeNode> Children { get; set; } = new();
}

var parent = new TreeNode { Name = "Parent" };
var child = new TreeNode { Name = "Child", Parent = parent };
parent.Children.Add(child);

parent.Dump();  // Safe!

Linked Lists

public class LinkedNode<T>
{
    public T Value { get; set; }
    public LinkedNode<T>? Next { get; set; }
    public LinkedNode<T>? Previous { get; set; }
}

var first = new LinkedNode<int> { Value = 1 };
var second = new LinkedNode<int> { Value = 2 };
first.Next = second;
second.Previous = first;

first.Dump();  // Safe!

Graph Structures

public class GraphNode
{
    public string Id { get; set; }
    public List<GraphNode> Connections { get; set; } = new();
}

var nodeA = new GraphNode { Id = "A" };
var nodeB = new GraphNode { Id = "B" };
var nodeC = new GraphNode { Id = "C" };

nodeA.Connections.Add(nodeB);
nodeB.Connections.Add(nodeC);
nodeC.Connections.Add(nodeA);  // Creates cycle

nodeA.Dump();  // Safe!

Controlling Depth

While circular references are handled, you may want to limit depth to keep output manageable:

// Limit to 3 levels
complexGraph.Dump(maxDepth: 3);

// Or set globally
DumpConfig.Default.MaxDepth = 3;

Entity Framework Relationships

Entity Framework navigation properties often create circular references:

public class Order
{
    public int Id { get; set; }
    public Customer Customer { get; set; }
}

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Order> Orders { get; set; }
}

// Customer.Orders[0].Customer points back to Customer
customer.Dump();  // Safe!

Best Practices

Use MaxDepth for Large Graphs

// Limit depth for complex object graphs
DumpConfig.Default.MaxDepth = 5;

Filter Navigation Properties

For EF entities, you might want to exclude navigation properties:

DumpConfig.Default.MembersConfig.MemberFilter = member =>
{
    // Skip navigation properties
    if (member.Name == "Customer" || member.Name == "Orders")
        return false;
    return true;
};

Use Labels to Identify Objects

node1.Dump("First Node");
node2.Dump("Second Node");

Technical Details

Dumpify uses object reference tracking (not value comparison) to detect cycles. This means:

  • Two objects with identical values are NOT considered circular
  • The same object instance referenced multiple times IS detected
  • Value types cannot be circular (they’re copied)
// Not circular - different instances with same values
var a = new Point { X = 1, Y = 2 };
var b = new Point { X = 1, Y = 2 };
new[] { a, b }.Dump();  // Both rendered fully

// Circular - same instance
var c = new Node { Value = 1 };
new[] { c, c }.Dump();  // Second reference detected

See Also