What are circular references in C#?

A circular reference in C# occurs when two or more objects reference each other directly or indirectly, creating a dependency loop. This can cause memory leaks and prevent proper garbage collection if not handled correctly.

The .NET garbage collector is designed to detect and handle circular references automatically. It uses a mark-and-sweep algorithm that starts from root objects (like static variables and local variables) and marks all reachable objects, then collects unmarked objects even if they have circular references.

Understanding Circular References

Circular references typically occur in parent-child relationships, linked lists, or complex object graphs where objects maintain references to each other.

Circular Reference Pattern Object A references B Object B references A A ? B B ? A Circular Dependency Objects keep each other alive even when no longer needed

Example of Circular Reference Problem

Here's a basic example where two classes reference each other −

using System;

public class Parent {
    public string Name;
    public Child MyChild;
    
    public Parent(string name) {
        Name = name;
    }
}

public class Child {
    public string Name;
    public Parent MyParent;
    
    public Child(string name) {
        Name = name;
    }
}

class Program {
    public static void Main() {
        Parent parent = new Parent("John");
        Child child = new Child("Alice");
        
        // Creating circular reference
        parent.MyChild = child;
        child.MyParent = parent;
        
        Console.WriteLine($"Parent: {parent.Name}, Child: {parent.MyChild.Name}");
        Console.WriteLine($"Child: {child.Name}, Parent: {child.MyParent.Name}");
        
        // Objects go out of scope but still reference each other
        parent = null;
        child = null;
        
        // Garbage collector will still clean them up
        GC.Collect();
        Console.WriteLine("Garbage collection completed");
    }
}

The output of the above code is −

Parent: John, Child: Alice
Child: Alice, Parent: John
Garbage collection completed

Breaking Circular References with Interfaces

One approach to avoid tight coupling in circular references is using interfaces to create loose coupling −

using System;

public interface IParent {
    string Name { get; }
}

public class Parent : IParent {
    public string Name { get; set; }
    public Child MyChild { get; set; }
    
    public Parent(string name) {
        Name = name;
    }
}

public class Child {
    public string Name { get; set; }
    public IParent MyParent { get; set; }  // Using interface
    
    public Child(string name) {
        Name = name;
    }
}

class Program {
    public static void Main() {
        Parent parent = new Parent("John");
        Child child = new Child("Alice");
        
        parent.MyChild = child;
        child.MyParent = parent;  // Assigned through interface
        
        Console.WriteLine($"Parent: {parent.Name}");
        Console.WriteLine($"Child's parent: {child.MyParent.Name}");
        Console.WriteLine("Interface-based reference established");
    }
}

The output of the above code is −

Parent: John
Child's parent: John
Interface-based reference established

Using Weak References

Another solution is using WeakReference to break the strong circular dependency −

using System;

public class Parent {
    public string Name;
    public WeakReference ChildRef;  // Weak reference
    
    public Parent(string name) {
        Name = name;
    }
    
    public Child GetChild() {
        return ChildRef?.Target as Child;
    }
}

public class Child {
    public string Name;
    public Parent MyParent;  // Strong reference upward
    
    public Child(string name) {
        Name = name;
    }
}

class Program {
    public static void Main() {
        Parent parent = new Parent("John");
        Child child = new Child("Alice");
        
        parent.ChildRef = new WeakReference(child);
        child.MyParent = parent;
        
        Console.WriteLine($"Parent: {parent.Name}");
        Console.WriteLine($"Child from weak ref: {parent.GetChild()?.Name}");
        
        // Child can be garbage collected even with parent alive
        child = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        
        Console.WriteLine($"After GC: {parent.GetChild()?.Name ?? "null"}");
    }
}

The output of the above code is −

Parent: John
Child from weak ref: Alice
After GC: null

How Garbage Collection Handles Circular References

The .NET garbage collector uses a mark-and-sweep algorithm that can detect and collect circular references automatically. It starts from root objects and marks all reachable objects, then collects unmarked objects regardless of their internal references.

Conclusion

Circular references in C# are automatically handled by the garbage collector, but can be managed more elegantly using interfaces for loose coupling or weak references to break strong dependency cycles. The key is understanding when these patterns are necessary for your specific use case.

Updated on: 2026-03-17T07:04:35+05:30

4K+ Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements