Не беспокойтесь о сборщике мусора; он легко справляется с графами ссылок с произвольной топологией. Беспокойство по поводу написания объектов, которые могут создавать ошибки, позволяя легко нарушать их инварианты.
Это сомнительный дизайн не потому, что он подчеркивает GC - это не так, - а потому, что он не обеспечивает желаемую семантику инвариант: что если X является родитель Y, тогда Y должен быть потомком X.
Может быть довольно сложно написать классы, которые поддерживают согласованные родительско-дочерние отношения. Что мы делаем в команде Roslyn, так это то, что мы на самом деле строим два дерева. «Настоящее» дерево имеет только дочерние ссылки; ни один ребенок не знает своего родителя. Мы накладываем «фасадное» дерево поверх того, которое обеспечивает согласованность отношений родитель-потомок: когда вы запрашиваете у родительского узла его дочерний элемент, он создает фасад поверх своего реального дочернего элемента и устанавливает родительский элемент этого объекта фасада. быть истинным родителем.
ОБНОВЛЕНИЕ: комментатор Брайан просит более подробную информацию. Вот эскиз того, как можно реализовать «красный» фасад с дочерними и родительскими ссылками над «зеленым» деревом, которое содержит только дочерние ссылки. В этой системе невозможно создать противоречивые отношения родитель-потомок, как вы можете видеть в тестовом коде внизу.
(Мы называем эти "красные" и "зеленые" деревья, потому что при рисовании структуры данных на доске это были те цвета маркеров, которые мы использовали.)
using System;
interface IValue { string Value { get; } }
interface IParent : IValue { IChild Child { get; } }
interface IChild : IValue { IParent Parent { get; } }
abstract class HasValue : IValue
{
private string value;
public HasValue(string value)
{
this.value = value;
}
public string Value { get { return value; } }
}
sealed class GreenChild : HasValue
{
public GreenChild(string value) : base(value) {}
}
sealed class GreenParent : HasValue
{
private GreenChild child;
public GreenChild Child { get { return child; } }
public GreenParent(string value, GreenChild child) : base(value)
{
this.child = child;
}
public IParent MakeFacade() { return new RedParent(this); }
private sealed class RedParent : IParent
{
private GreenParent greenParent;
private RedChild redChild;
public RedParent(GreenParent parent)
{
this.greenParent = parent;
this.redChild = new RedChild(this);
}
public IChild Child { get { return redChild; } }
public string Value { get { return greenParent.Value; } }
private sealed class RedChild : IChild
{
private RedParent redParent;
public RedChild(RedParent redParent)
{
this.redParent = redParent;
}
public IParent Parent { get { return redParent; } }
public string Value
{
get
{
return redParent.greenParent.Child.Value;
}
}
}
}
}
class P
{
public static void Main()
{
var greenChild1 = new GreenChild("child1");
var greenParent1 = new GreenParent("parent1", greenChild1);
var greenParent2 = new GreenParent("parent2", greenChild1);
var redParent1 = greenParent1.MakeFacade();
var redParent2 = greenParent2.MakeFacade();
Console.WriteLine(redParent1.Value); // parent1
Console.WriteLine(redParent1.Child.Parent.Value); // parent1 !
Console.WriteLine(redParent2.Value); // parent2
Console.WriteLine(redParent2.Child.Parent.Value); // parent2 !
// See how that goes? RedParent1 and RedParent2 disagree on what the
// parent of greenChild1 is, **but they are self-consistent**. They
// always report that the parent of their child is themselves.
}
}