GC будет собирать любой объект, на который не ссылается корневой объект. Корневыми объектами обычно являются объекты, на которые ссылаются все домены приложений (т.е. загруженные сборки / объекты типа), глобальные и статические ссылки на объекты, все ссылки на объекты в соответствующем стеке каждого потока плюс любые ссылки на объекты, загруженные в данный момент в регистр ЦП.
Во время сбора GC просматривает ссылки на все известные корневые объекты и помечает любой объект, который он находит по пути, как «используемый». Когда все сделано, любые объекты, не отмеченные, могут быть безопасно собраны.
Таким образом, если ни один из ваших объектов не ссылается (прямо или косвенно) на корневой объект, не имеет значения, существуют ли циклические ссылки. Все они будут иметь право на сбор независимо. Я говорю «право», потому что сборщик мусора использует три разные стратегии для сбора подходящих объектов, и по соображениям производительности только одна из них собирает все подходящие объекты при запуске.
Обычно вам не нужно об этом думать, это просто работает. Но есть еще много возможностей для сбора мусора, и вам все равно нужно понимать его основы, чтобы понимать управление памятью и писать код без ошибок, который не пропускает ресурсы и т. Д. Поэтому каждый разработчик .NET должен обратить на это внимание. Вот несколько ресурсов, которые объясняют, как CLR выполняет сборку мусора.
MSDN - Основы работы сборщика мусора и советы по производительности (Rico Mariani)
MSDN Magazine - Сборка мусора (часть 1): Автоматическое управление памятью в Microsoft .NET Framework (Джеффри Рихтер)
MSDN Magazine - Сборка мусора (часть 2): Автоматическое управление памятью в Microsoft .NET Framework (Джеффри Рихтер)