Круговая ссылка в той же сборке плохая вещь?


Предположим, что у меня есть следующие классы в той же сборке

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 13

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 может справиться с этим, так почему это должно быть проблемой? Если он обеспечивает необходимую функциональность, то он должен быть в порядке.

Вы должны опасаться проблем с синхронизацией свойств, предполагая, что это будет проблемой с нашим кодом.