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


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

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()до он полностью построен? Я думаю, что объект полностью построен только после того, как логика управления оставляет конструктор.

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

8 63

8 ответов:

мой вопрос в первом () конструкторе, разве это не плохо, что мы отправляем ссылку на класс First (), прежде чем он будет полностью построен?

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

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

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

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

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

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

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

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

public class First()
{
      private Second _second;

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

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

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

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

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

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

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

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

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

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

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

ответ на этот вопрос зависит от характера отношений между 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.)