Инициализировать поля класса в конструкторе или при объявлении?

Я недавно программировал на С# и Java, и мне любопытно, где лучше всего инициализировать поля моего класса.

Должен ли я сделать это при объявлении?:

public class Dice
{
    private int topFace = 1;
    private Random myRand = new Random();

    public void Roll()
    {
       // ......
    }
}

или в конструкторе?:

public class Dice
{
    private int topFace;
    private Random myRand;

    public Dice()
    {
        topFace = 1;
        myRand = new Random();
    }

    public void Roll()
    {
        // .....
    }
}

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

Ответ 1

Мои правила:

  • Не инициализируйте значения по умолчанию в объявлении (null, false, 0, 0.0...).
  • Предпочитайте инициализацию в объявлении, если у вас нет параметра конструктора, который изменяет значение поля.
  • Если значение поля изменяется из-за параметра конструктора, поместите инициализацию в конструкторы.
  • Будьте последовательны в своей практике (самое важное правило).

Ответ 2

В С# это не имеет значения. Два примера кода, которые вы даете, абсолютно эквивалентны. В первом примере компилятор С# (или это CLR?) Создаст пустой конструктор и инициализирует переменные, как если бы они были в конструкторе (в этом есть небольшой нюанс, который объясняет Джон Скит в комментариях ниже). Если конструктор уже существует, любая инициализация "выше" будет перемещена в верхнюю его часть.

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

Ответ 3

Семантика С# немного отличается от Java. В назначении С# в объявлении выполняется перед вызовом конструктора суперкласса. В Java это делается сразу же после чего позволяет использовать 'this' (особенно полезно для анонимных внутренних классов) и означает, что семантика двух форм действительно соответствует.

Если вы можете, сделайте поля окончательными.

Ответ 4

Я думаю, что есть одно предостережение. Я однажды совершил такую ​​ошибку: внутри производного класса я попытался "инициализировать при объявлении" поля, унаследованные от абстрактного базового класса. Результатом было то, что существовало два набора полей, один - "базовый", а другой - недавно объявленные, и мне пришлось потратить некоторое время на отладку.

Урок: для инициализации полей наследуемых вы должны сделать это внутри конструктора.

Ответ 5

Предполагая, что тип в вашем примере определенно предпочитает инициализировать поля в конструкторе. Исключительные случаи:

  • Поля в статических классах/методах
  • Поля, введенные как static/final/et al

Я всегда думаю о списке полей в верхней части класса в качестве оглавления (что содержится здесь, а не о том, как он используется), а конструктор - как введение. Методы, конечно, являются главами.

Ответ 6

Что, если бы я сказал вам, это зависит?

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

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

В системе реального времени я задаю вопрос, нужна ли мне вообще переменная или константа.

И в С++ я часто делаю рядом без инициализации в любом месте и переместите его в функцию Init(). Зачем? Ну, на С++, если вы инициализируете что-то, что может генерировать исключение при построении объекта, вы открываете себя для утечек памяти.

Ответ 7

Существует много разных ситуаций.

Мне нужен пустой список

Ситуация ясна. Мне просто нужно подготовить список и предотвратить исключение исключения, когда кто-то добавляет элемент в список.

public class CsvFile
{
    private List<CsvRow> lines = new List<CsvRow>();

    public CsvFile()
    {
    }
}

Я знаю значения

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

public class AdminTeam
{
    private List<string> usernames;

    public AdminTeam()
    {
         usernames = new List<string>() {"usernameA", "usernameB"};
    }
}

или

public class AdminTeam
{
    private List<string> usernames;

    public AdminTeam()
    {
         usernames = GetDefaultUsers(2);
    }
}

Пустой список с возможными значениями

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

public class AdminTeam
{
    private List<string> usernames = new List<string>();

    public AdminTeam()
    {
    }

    public AdminTeam(List<string> admins)
    {
         admins.ForEach(x => usernames.Add(x));
    }
}

Ответ 8

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

Ответ 9

Конструкция С# предполагает, что предпочтительна инициализация в линии, или она не будет на языке. Каждый раз, когда вы можете избежать перекрестной ссылки между разными местами в коде, вам обычно лучше.

Также существует вопрос о согласованности с инициализацией статического поля, которая должна быть встроенной для лучшей производительности. Руководство по разработке рамок для Конструктор конструктора скажите это:

✓ CONSIDER инициализирует статические поля inline, а не явно использует статические конструкторы, поскольку среда выполнения может оптимизировать производительность типов, которые не имеют явно заданного статического конструктора.

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

Ответ 10

Будучи последовательным, важно, но это вопрос, который нужно задать себе: "У меня есть конструктор для чего-нибудь еще?"

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

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

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

Ответ 11

Рассмотрим ситуацию, когда у вас более одного конструктора. Будет ли инициализация разной для разных конструкторов? Если они будут одинаковыми, то зачем повторять для каждого конструктора? Это соответствует заявлению kokos, но не может быть связано с параметрами. Скажем, например, вы хотите сохранить флаг, который показывает, как был создан объект. Тогда этот флаг будет инициализирован по-разному для разных конструкторов независимо от параметров конструктора. С другой стороны, если вы повторяете ту же инициализацию для каждого конструктора, вы оставляете возможность, что вы (непреднамеренно) изменяете параметр инициализации в некоторых конструкторах, но не в других. Итак, основная концепция здесь заключается в том, что общий код должен иметь общее местоположение и не быть потенциально повторенным в разных местах. Поэтому я бы сказал, всегда помещал его в декларацию, пока у вас не было конкретной ситуации, когда это больше не работает для вас.

Ответ 12

Существует небольшое преимущество в производительности для установки значения в объявлении. Если вы установите его в конструкторе, он фактически устанавливается дважды (сначала значение по умолчанию, затем reset в ctor).

Ответ 13

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

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

Ответ 14

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

class MyGeneric<T>
{
    T data;
    //T data = ""; // <-- ERROR
    //T data = 0; // <-- ERROR
    //T data = null; // <-- ERROR        

    public MyGeneric()
    {
        // All of the above errors would be errors here in constructor as well
    }
}

И специальный метод для инициализации универсального поля его значением по умолчанию следующий:

class MyGeneric<T>
{
    T data = default(T);

    public MyGeneric()
    {           
        // The same method can be used here in constructor
    }
}