Циркуляр в той же сборке это плохо? - PullRequest
13 голосов
/ 07 марта 2012

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

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;
   }

}

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

Редактировать

Что если два класса в конечном итоге привыкнут, как это, в каком-то другом классе?

ParentClass objP = new ParentClass ();
ChildClass objC =new ChildClass(objP);
objP.Child = objC;

Мысли, пожалуйста ....

Ответы [ 4 ]

18 голосов
/ 07 марта 2012

Не беспокойтесь о сборщике мусора; он легко справляется с графами ссылок с произвольной топологией. Беспокойство по поводу написания объектов, которые могут создавать ошибки, позволяя легко нарушать их инварианты.

Это сомнительный дизайн не потому, что он подчеркивает 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.
    }
}
11 голосов
/ 07 марта 2012

Это не особенно плохой дизайн и не проблема с технической точки зрения.Экземпляры класса (объекты) являются ссылочными типами, то есть _parent и _child содержат только ссылку на соответствующий объект, но не сам объект.Таким образом, вы не вызываете какую-то бесконечную структуру данных.

Как вы писали сами, сборщик мусора способен обрабатывать циклические ссылки, главным образом потому, что не использует подсчет ссылок.

Единственная «проблема» с такой структурой, как правило, состоит в том, что вы часто хотите синхронизировать оба конца отношения, т.е.данный ребенок c и родитель p тогда p.Child == c if and only if c.Parent == p.Вам нужно решить, как справиться с этим наилучшим образом.Например, если у вас есть метод Parent.AddChild (Child c), он может не только установить Parent.Child в c, но также Child.Parent в родительский.Но что, если Child.Parent назначен напрямую?Вот некоторые вопросы, с которыми вам, возможно, придется иметь дело.

3 голосов
/ 07 марта 2012

Это в целом отлично IMO.
MS, например, делает это в большинстве элементов управления / элементов пользовательского интерфейса, например, Form имеет коллекцию Controls, и каждый элемент управления в этой коллекции имеет свойство ParentForm.

2 голосов
/ 07 марта 2012

Это нормально, я думаю, и полезно, если вам нужно уметь ориентироваться в отношениях в обоих направлениях.

Как вы указали, GC может справиться с этим, так почему это должно быть проблемой? Если он обеспечивает необходимую вам функциональность, то все должно быть в порядке.

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

...