Подтвердить что ты не робот

Циркулярная ссылка в том же сборке плохая вещь?

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

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;

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

4b9b3361

Ответ 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.
    }
}

Ответ 2

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

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

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

Ответ 3

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

Ответ 4

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

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

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