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

Отправка ссылки объекта перед его строительством

Я видел следующий код в одном из наших приложений:

public class First()
{
      private Second _second;

      public First()
      {
          _second = new Second(this);
          // Doing some other initialization stuff,
      }

}

public class Second
{
    public Second(First f)
    {
    }
}

В конструкторе First() не так ли плохо, что мы отправляем ссылку класса First() до того, как она будет полностью построена? Я думаю, что объект полностью построен только после того, как логика управления покидает конструктор.

Или это нормально?

4b9b3361

Ответ 1

Мой вопрос: в конструкторе First() не так ли плохо, что мы отправляем ссылку класса First() ДО ТОГО, что она полностью построена?

Несколько. Это может быть проблемой, конечно.

Если конструктор Second просто держит ссылку на последующее использование, это не так уж плохо. Если, с другой стороны, конструктор Second обращается к First:

public Second(First f)
{
    f.DoSomethingUsingState();
}

... и состояние еще не настроено, тогда это, конечно же, будет очень плохая вещь. Если вы вызываете виртуальный метод на First, тогда это может быть еще хуже - вы можете в конечном итоге вызвать код, который даже не имел возможности запустить какой-либо из его тела конструктора (хотя его инициализаторы переменной будут выполнены).

В частности, поля readonly видны сначала с одним значением, а затем с другим...

I писал об этом некоторое время назад, что может предоставить дополнительную информацию.

Конечно, не делая такого рода вещи, довольно сложно создать два взаимно-ссылочных неизменяемых объекта...

Ответ 2

Если вы столкнулись с этим шаблоном, вы можете проверить, может ли он быть реорганизован на него:

public class First()
{
      private Second _second;

      public First()
      {
          _second = new Second(this);
          // Doing some other initialization stuff,
      }

      private class Second
      {
          public Second(First f)
          {
          }
      }
}

Передача зависимости в конструктор подразумевает некоторую жесткую связь между этими двумя классами, поскольку First должен полагать, что Second знает, что она делает, и не будет пытаться полагаться на неинициализированное состояние First. Такая сильная связь более подходит, если Second является частным вложенным подклассом (и, следовательно, четкой деталью реализации) или, возможно, когда он является внутренним классом.

Ответ 3

Ответ: это зависит. В общем, однако, это будет считаться плохой идеей из-за возможных последствий.

В частности, хотя до тех пор, пока Second фактически ничего не использует от First до его создания, тогда вы должны быть в порядке. Если вы не можете гарантировать, что так или иначе, вы наверняка столкнетесь с проблемами.

Ответ 4

Да, это немного плохо. До того, как он будет полностью инициализирован, возможно сделать что-то в области First, что приведет к поведению нежелательного или undefined.

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

Ответ 5

В отличие от, например, С++, CLR не имеет понятия полностью построенных или не полностью сконструированных объектов. Как только распределитель памяти возвращает объект с нулевым значением и до запуска конструктора, он готов к использованию (с точки зрения CLR). Он имеет свой конечный тип, вызовы виртуальных методов вызывают наиболее производные переопределения и т.д. Вы можете использовать this в теле конструктора, вызывать виртуальные методы и т.д. Это может действительно вызвать проблемы с порядком инициализации, но в CLR нет ничего, что могло бы предотвратить их.

Ответ 6

Правда, это может привести к проблемам, описанным вами. Поэтому обычно рекомендуется запускать команды, такие как _second = new Second(this);, только после того, как вы добавили другой материал инициализации, указанный вашим комментарием.

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

Ответ 7

Это зависит от сценария, но это может привести к сложному прогнозированию поведения. Если Second делает что-либо с First в конструкторе, это поведение может стать нечетким, если вы измените конструктор First. Дополнительное руководство конструктора также предполагает, что вы не должны вызывать виртуальные или абстрактные методы (по построенному классу) в конструкторе, потому что это может привести к аналогичным последствиям, когда поведение может быть трудно объяснить.

Ответ 8

Ответ на этот вопрос зависит от характера отношения между First и Second.

Подумайте, какой объект может состоять из другого объекта, который сам состоит из (или требует для его инициализации) объекта типа First. В подобных ситуациях вы должны опасаться создания графов объектов с циклами.

Тем не менее, существует множество законных ситуаций, в которых цикл должен происходить в графе объектов. Если First полагается на состояние Second для выполнения своей инициализации, вы должны сохранить метод как есть, и это вообще нормально. Если Second полагается на состояние First для выполнения своей собственной инициализации, то вы, вероятно, должны перестроить конструктор как таковой:

  public First()
  {

      // Doing some other initialization stuff,
      _second = new Second(this);
  }

Если оба из предыдущих утверждений истинны (Second зависит от состояния First, а First зависит от состояния Second), вы должны почти наверняка пересмотреть свой дизайн и выяснить более точно характер отношения между First и Second. (Может быть, должен быть какой-то объект Third, который содержит ссылку как на First, так и на Second, а связь между двумя последними должна быть решена с помощью Third.)