Круговая ссылка в той же сборке плохая вещь?
Предположим, что у меня есть следующие классы в той же сборке
public class ParentClass : IDisposable
{
public ChildClass Child
{
get { return _child; }
}
}
public class ChildClass
{
public ParentClass Parent
{
get { return _parent; }
set { _parent= value; }
}
public ChildClass (ParentClass parent)
{
Parent= parent;
}
}
Поправьте меня, если я ошибаюсь, но это плохой дизайн. Приведет ли это к утечке памяти или каким-то другим непредвиденным проблемам позже? По-видимому, сборщик мусора способен обрабатывать такого рода циклические ссылки.
Edit
Что, если эти два класса в конечном итоге будут использоваться подобным образом в каком-то другом классе?
ParentClass objP = new ParentClass ();
ChildClass objC =new ChildClass(objP);
objP.Child = objC;
Мысли, пожалуйста ....
4 ответа:
Не беспокойтесь о сборщике мусора; он легко обрабатывает ссылочные графики с произвольными топологиями. Беспокойтесь о написании объектов, которые поддаются созданию ошибок, позволяя легко нарушать их инварианты.
Это сомнительная конструкция не потому, что она подчеркивает 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. } }
Это не особенно плохой дизайн и не проблема с технической точки зрения. Экземпляры класса (объекты) являются ссылочными типами, то есть _parent и _child содержат только ссылку на соответствующий объект, а не на сам объект. Таким образом, вы не создаете какую-то бесконечную структуру данных.
Как вы уже писали, сборщик мусора способен обрабатывать циклические ссылки, главным образом потому, что он не использует подсчет ссылок.
Единственная "проблема" с такого рода структурой, как правило, только это то, что вы часто хотите, чтобы оба конца отношения были синхронизированы, т. е. дано дочернее c и родительское p, то
p.Child == c if and only if c.Parent == p
. Вам нужно решить, как справиться с этим наилучшим образом. Например, если у вас есть метод Parent.AddChild (Child c) это может быть не только набор Parent.Ребенок К С, но и ребенок тоже.Родитель к родителю. Но что, если ребенок.Родитель назначается непосредственно? Это некоторые вопросы, с которыми вам, возможно, придется иметь дело.
Это совершенно нормально IMO.
MS в качестве примера делает это в большинстве элементов управления/элементов пользовательского интерфейса, например,Form
имеет коллекциюControls
, и каждый элемент управления в этой коллекции имеет свойствоParentForm
.
Это нормально, я думаю, и полезно, если вам нужно иметь возможность ориентироваться в отношениях в обоих направлениях.
Как вы указали, GC может справиться с этим, так почему это должно быть проблемой? Если он обеспечивает необходимую функциональность, то он должен быть в порядке.
Вы должны опасаться проблем с синхронизацией свойств, предполагая, что это будет проблемой с нашим кодом.