Вложенное использование инициализаторов объектов C # - PullRequest
8 голосов
/ 28 апреля 2009

Итак, инициализаторы объектов очень удобны - особенно если вы делаете linq, где они совершенно необходимы - но я не могу понять это:

public class Class1 {
   public Class2 instance;
}

public class Class2 {
   public Class1 parent;
}

используя вот так:

Class1 class1 = new Class1();
class1.instance = new Class2();
class1.parent = class1;

в качестве инициализатора:

Class1 class1 = new Class1() {
    instance = new Class2() {
        parent = class1
    }
};

это не работает, class1 предположительно является неназначенной локальной переменной. в Linq становится еще сложнее, когда вы делаете что-то вроде

select new Class1() { ...

у него даже нет имени, на которое можно сослаться!

как мне обойти это? можно просто не создавать вложенные ссылки с помощью инициализаторов объектов?

Ответы [ 6 ]

7 голосов
/ 28 апреля 2009

можно ли просто не создавать вложенные ссылки с помощью инициализаторов объектов?

Вы правы - вы не можете. Был бы цикл; A требует B для инициализации, но B требует A раньше. Если быть точным - вы, конечно, можете создавать инициализаторы вложенных объектов, но не с циклическими зависимостями.

Но вы можете - и я бы посоветовал вам по возможности - обойти это следующим образом.

public class A
{
   public B Child
   {
      get { return this.child; }
      set
      {
         if (this.child != value)
         {
            this.child = value;
            this.child.Parent = this;
         }
      }
   }
   private B child = null;
}

public class B
{
   public A Parent
   {
      get { return this.parent; }
      set
      {
         if (this.parent != value)
         {
            this.parent = value;
            this.parent.Child = this;
         }
      }
   }
   private A parent = null;
}

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

b.Parent = a;
a.Child = b;

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

a.Child = b;

Или наоборот.

b.Parent = a;

И, наконец, с синтаксисом инициализатора объекта.

A a = new A { Child = new B() };
2 голосов
/ 28 апреля 2009

Вы не можете сделать это с помощью инициализаторов объектов. Тем не менее, вы можете сделать трюк, используя правильный код:

class A
{
    B b;

    public B B
    {
        set
        {
            b = value;
            b.a = this;
        }
        get
        {
            return b;
        }
    }
}

class B
{
    public A a;
}

Называя это:

var a = new A  {  B = new B() };
1 голос
/ 28 апреля 2009

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

        Class1 myClass1 = null;

        myClass1 = new Class1()
        {
            instance = new Class2
            {
                parent = myClass1
            }
        };

Это скомпилируется и запустится, но родительское свойство Class2 будет иметь значение null, поскольку это было то значение, которое было на момент запуска внутренней строки кода, то есть самая внутренняя строка выполнялась первой.

1 голос
/ 28 апреля 2009

Эта проблема не характерна для инициализаторов объектов, это общее ограничение в языке C #. Вы не можете использовать локальную переменную, пока она не будет определенно назначена. Вот более простое воспроизведение

Class1 Foo(Class1 c1) {
  return c1;
}

void Example() {
  Class1 c1 = Foo(c1);
}
0 голосов
/ 28 апреля 2009

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

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

Например:

public class ParentClass 
{
    public List<ChildClass> Children;
    public void AddChild(ChildClass child)
    {
        Children.Add(child);
        // or something else, etc. 
    }
    // various stuff like making sure Children actually exists before AddChild is called
}

public class ChildClass 
{
    public ParentClass Parent;
    public ChildClass(ParentClass parent)
    {
        Parent = parent;
        Parent.AddChild(this);
    }
}

Тогда в вашем телефонном коде:

var parent = new ChildClass(new ParentClass()).Parent;

И, да, это работает в LINQ:

// qry, etc.
select new ChildClass(new ParentClass()).Parent

но тогда как мне сделать все ChildClass имеет тот же ParentClass экземпляр? - Энди Хохорст

Тогда вы должны знать родительский класс заранее.

var parent = new ParentClass();
var child = new ChildClass(parent);

или

var parent = new ParentClass();
// qry, etc.
select new ChildClass(parent)
0 голосов
/ 28 апреля 2009

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...