Ответ 1
Не беспокойтесь о сборщике мусора; он легко справляется с графическими графами с произвольными топологиями. Беспокоитесь о создании объектов, которые могут создавать ошибки, позволяя легко нарушать их инварианты.
Это сомнительный дизайн не потому, что он подчеркивает GC - это не так, а скорее потому, что он не обеспечивает требуемого семантического инварианта: что если X является родительским элементом Y, то Y должно быть дочерним элементом X.
Может быть довольно сложно писать классы, которые поддерживают согласованные отношения родитель-потомок. Что мы делаем в команде Roslyn, мы фактически строим два дерева. "Реальное" дерево имеет только дочерние ссылки; ни один ребенок не знает своего родителя. Мы добавляем дерево "фасада" поверх того, что обеспечивает согласованность отношений родитель-потомок: когда вы запрашиваете родительский node для своего дочернего элемента, он создает фасад поверх своего реального дочернего элемента и устанавливает родительский элемент этого объект фасада должен быть истинным родителем.
ОБНОВЛЕНИЕ: комментатор Брайан просит получить более подробную информацию. Здесь приведен пример того, как вы можете реализовать "красный" фасад с дочерними и родительскими ссылками над "зеленым" деревом, содержащим только ссылки на дочерние ссылки. В этой системе невозможно установить несогласованные отношения родитель-потомок, как вы можете видеть в тестовом коде внизу.
(Мы называем эти "красные" и "зеленые" деревья, потому что при рисовании структуры данных на доске это были цвета маркеров, которые мы использовали.)
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.
}
}