次の方法で共有


Вызовы конструкторов в произвольных местах

В C# вы можете вызвать один конструктор из другого, но только перед выполнением тела вызывающего конструктора:

public C(int x) : this(x, null)
{
  // …
}
public C(int x, string y)
{
  // …
}

Почему можно вызвать конструктор перед телом конструктора, но не в его конце или середине?

Давайте рассмотрим два случая. (1) Вы вызываете конструктор базового класса (с помощью ключевого слова “base”) и (2) вы вызываете конструктор текущего класса (с помощью ключевого слова “this”).

Для случая вызова конструктора базового класса все достаточно просто. Вам практически никогда не нужно вызывать конструктор базового класса после конструктора производного, поскольку это изменяет нормальные правила зависимости. Код производного класса может полагаться на то, что конструктор базового класса проинициализировал «базовое» состояние объекта. В то же время, конструктор базового класса не должен полагаться на то, что конструктор производного класса проинициализировал «производное» состояние.

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

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

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

1) Наличие двух путей решения одной и той же задачи приводит к замешательству и требует больших умственных усилий. В языке C# у нас часто имеется два пути решения одной и той же задачи, но в таких случаях мы хотим, чтобы эта ситуация окупалась наличием разных путей решения, каждый из которых будет обладать убедительными, интересными и мощными возможностями с явными преимуществами и недостатками. (Например, «синтаксис запросов» – query comprehensions и «синтаксис на основе методов» – fluent queries – это два различных способа построения запросов.) Возможность вызова конструктора таким же образом, что и вызов любых других методов, очень похожа на наличие двух различных путей решения одной задачи – вызова метода инициализации, но без убедительных и интересных преимуществ.

2) Для реализации этого нам придется добавлять в язык новый синтаксис. Цена добавления нового синтаксиса очень высока. Для этого требуется проектирование, реализация, тестирование, документирование – а все это требует наших затрат. Но еще большие затраты ложатся на вас, потому что вам нужно выучить, что означает этот синтаксис, в противном случае вы не сможете читать и поддерживать код других разработчиков. Об этих затратах также не нужно забывать. Опять-таки, мы готовы смириться с огромными затратами, только когда чувствуем, что получим явные, убедительные и значительные преимущества для пользователей. В этом случае значительных преимуществ я не вижу.

Короче говоря, добиться необходимого контроля над созданием объекта можно весьма просто без добавления новых возможностей; убедительных преимуществ добавления такой возможности не существует. Поэтому никакая новая интересная представительная возможность в язык добавлена не будет.

Оригинал статьи