C# 순환 참조(Circular Reference)란 무엇인가?
순환 참조(Circular Reference)는 두 개 이상의 객체가 서로를 참조하면서 의존 관계가 꼬리를 물고 이어지는 상황을 말합니다. 이러한 상태가 지속되면 리소스가 잠금(lock) 상태에 빠져 더 이상 정상적으로 사용할 수 없게 되는 문제가 발생합니다.
가비지 컬렉션(Garbage Collection)으로 순환 참조 처리하기
C#에서는 가비지 컬렉터(Garbage Collector)를 통해 순환 참조 문제를 효과적으로 해결할 수 있습니다. .NET의 가비지 컬렉터는 도달 가능성(reachability) 기반으로 동작하기 때문에, 서로 참조하는 객체라도 외부에서 접근할 수 없다면 자동으로 감지하여 수거합니다.
가비지 컬렉션의 동작 원리는 다음과 같습니다.
- 가비지 컬렉터는 로컬 변수와 정적(static) 변수를 루트(root)로 삼아 탐색을 시작합니다.
- 루트에서 시작해 각 객체가 참조하는 자식 객체들을 따라가며 도달 가능한 모든 객체에 마킹(marking)을 합니다.
- 마킹되지 않은 객체, 즉 어떤 경로로도 도달할 수 없는 객체는 설령 다른 객체와 상호 참조 중이더라도 불필요한 것으로 판단하고 메모리에서 수거합니다.
이러한 마킹 방식 덕분에 C#은 순환 참조가 존재하더라도 메모리 누수 없이 안전하게 객체를 정리할 수 있습니다.
순환 참조 발생 예시
다음 코드처럼 두 클래스가 서로를 필드로 참조하는 경우를 살펴보겠습니다. 클래스 A와 B는 각각 상대방 타입의 객체를 멤버로 가지고 있어 완전한 순환 의존 관계를 형성합니다.
public class A {
B Two;
}
public class B {
A one;
}인터페이스를 활용한 해결 방법
이러한 강한 결합(strong coupling) 구조는 유연성을 떨어뜨리고 설계상 문제를 일으킬 수 있습니다. 이를 해결하는 대표적인 방법은 인터페이스를 도입하는 것입니다. 인터페이스를 사용하면 클래스 간 직접적인 의존을 끊고 느슨한 결합(loose coupling) 구조로 바꿀 수 있습니다.
public interface myInterface {
}
public class A {
myInterface Two;
}
public class B : myInterface {
A one;
}위 코드처럼 B 클래스가 myInterface를 구현하도록 하면, A 클래스는 구체적인 B 타입이 아닌 인터페이스 타입만 알면 됩니다. 이렇게 하면 순환 의존 관계가 완화되고, 코드의 확장성과 유지보수성이 크게 향상됩니다.
정리
순환 참조는 객체들이 서로를 참조해 리소스가 잠기는 현상입니다. 다행히 C#의 가비지 컬렉터는 루트에서부터 도달 가능 여부를 판단해 순환 참조 객체까지 자동으로 수거해 주므로 메모리 관리 측면에서는 비교적 안전합니다. 다만 설계 차원에서는 인터페이스 도입 등을 통해 불필요한 순환 의존을 제거하는 것이 좋은 아키텍처를 만드는 핵심입니다.