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

Я недавно программировал на C # и 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()
    {
        // .....
    }
}

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

411 java
23.08.2008 19:59:42
Обратите внимание, что для структур у вас не может быть инициализаторов полей экземпляра, поэтому у вас нет выбора, кроме как использовать конструктор.
yoyo 10.07.2014 23:45:00
Так как вы воспитали «лучшую практику»: assignice.com/blog/archives/5164 , forbes.com/sites/mikemyatt/2012/08/15/best-practices-arent/…
Stephen C 21.02.2020 02:27:57
15 ОТВЕТОВ
РЕШЕНИЕ

Мои правила:

  1. Не инициализировать значения по умолчанию в объявлении ( null, false, 0, 0.0...).
  2. Предпочитайте инициализацию в объявлении, если у вас нет параметра конструктора, который изменяет значение поля.
  3. Если значение поля изменяется из-за параметра конструктора, поместите инициализацию в конструкторы.
  4. Будьте последовательны в своей практике (самое важное правило).
308
16.12.2015 09:28:15
Я ожидаю, что kokos означает, что вы не должны инициализировать элементы к их значениям по умолчанию (0, false, null и т. Д.), Так как компилятор сделает это за вас (1.). Но если вы хотите инициализировать поле чем-то отличным от его значения по умолчанию, вы должны сделать это в объявлении (2.). Я думаю, что это может быть использование слова «по умолчанию», которое смущает вас.
Ricky Helgesson 11.10.2012 17:12:45
Я не согласен с правилом 1 - не указывая значение по умолчанию (независимо от того, инициализировано ли оно компилятором или нет), вы оставляете разработчику угадывать или искать документацию о том, каким будет значение по умолчанию для этого конкретного языка. Для удобства чтения я бы всегда указывал значение по умолчанию.
James 9.01.2013 12:50:11
Значением по умолчанию для типа default(T)всегда является значение, которое имеет внутреннее двоичное представление 0.
Olivier Jacot-Descombes 13.03.2013 19:26:15
Независимо от того, нравится ли вам правило 1, его нельзя использовать с полями только для чтения, которые должны быть явно инициализированы к моменту завершения конструктора.
yoyo 10.07.2014 23:39:37
Я не согласен с теми, кто не согласен с правилом 1. Можно ожидать, что другие выучат язык C #. Точно так же, как мы не комментируем каждый foreachцикл словами «это повторяет следующее для всех элементов в списке», нам не нужно постоянно пересчитывать значения по умолчанию в C #. Нам также не нужно делать вид, что C # имеет неинициализированную семантику. Поскольку отсутствие значения имеет четкое значение, его можно не указывать. Если это идеально, чтобы быть явным, вы всегда должны использовать newпри создании новых делегатов (как это требовалось в C # 1). Но кто это делает? Язык предназначен для добросовестных программистов.
Edward Brey 14.05.2017 05:24:30

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

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

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

6
23.08.2008 21:56:29
Почему это так? Вы предоставили только предпочтение стиля без объяснения причин. Ой , подождите, не говоря уже , по @quibblesome «s ответ , они„совершенно эквивалентны“, так что это действительно просто личные предпочтения стиля.
RayLuo 3.05.2017 23:45:12

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

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

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

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

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

4
10.04.2012 19:46:15

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

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

0
24.08.2008 15:59:11

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

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

149
7.03.2019 14:59:04
Это не правильно, если вы решите инициализировать класс с помощью GetUninitializedObject. Все, что находится в ctor, не будет затронуто, но объявления полей будут запущены.
Wolf5 3.01.2013 09:18:11
На самом деле это имеет значение. Если конструктор базового класса вызывает виртуальный метод (который обычно плохая идея, но может случиться), который переопределяется в производном классе, то при использовании инициализаторов переменных экземпляра переменная будет инициализирована при вызове метода - тогда как при инициализации в конструктор, они не будут. (Инициализаторы переменных экземпляра выполняются до вызова конструктора базового класса.)
Jon Skeet 13.08.2013 08:52:10
В самом деле? Клянусь, я получил эту информацию из CLR Рихтера через C # (второе издание, я думаю), и суть его заключалась в том, что это был синтаксический сахар (я, возможно, прочитал его неправильно?), А CLR просто заклинило переменные в конструкторе. Но вы утверждаете, что это не так, то есть инициализация члена может сработать до инициализации ctor в безумном сценарии вызова виртуального базового ctor и наличия переопределения в рассматриваемом классе. Правильно ли я понял? Вы только что узнали это? Озадаченный этим актуальным комментарием к 5-летнему сообщению (OMG это было 5 лет?).
Quibblesome 13.08.2013 19:18:44
@Quibblesome: дочерний конструктор класса будет содержать цепочечный вызов родительского конструктора. Языковой компилятор может включать в себя столько кода или меньше, сколько ему угодно, при условии, что родительский конструктор вызывается ровно один раз во всех путях кода, и до этого вызова строится ограниченный объект, находящийся в процессе конструирования. Одна из моих неприятностей с C # заключается в том, что, хотя он может генерировать код, который инициализирует поля, и код, который использует параметры конструктора, он не предлагает механизма для инициализации полей на основе параметров конструктора.
supercat 20.02.2015 21:02:26
@Quibblesome, прежний будет проблемой, если значение назначено и передано методу WCF. Который сбросит данные к типу данных модели по умолчанию. Лучше всего выполнить инициализацию в конструкторе (позже).
Pranesh Janarthanan 23.12.2019 15:50:58

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

1
29.08.2008 15:47:24
В C # поля всегда устанавливаются значения по умолчанию в первую очередь. Наличие инициализатора не имеет значения.
Jeffrey L Whitledge 10.05.2010 18:20:13

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

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

16
5.09.2008 20:22:08

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

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

14
11.05.2016 09:23:17
Так что, если вы ссылаетесь на поле derivedObject.InheritedField, относится ли оно к вашему базовому или производному?
RayLuo 3.05.2017 23:39:38

Есть много разных ситуаций.

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

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

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));
    }
}
4
18.09.2016 18:42:07

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

3
17.01.2016 20:13:38

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

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

✓ УЧИТЫВАЙТЕ инициализацию статических полей встроенными, а не явно используя статические конструкторы, потому что среда выполнения способна оптимизировать производительность типов, которые не имеют явно определенного статического конструктора.

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

3
14.05.2017 05:46:59

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

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

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

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

2
24.05.2017 20:27:19

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

2
20.10.2017 21:05:40

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

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
    }
}
0
8.07.2018 12:32:06

Когда вам не нужна логика или обработка ошибок:

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

Когда вам нужна логика или обработка ошибок:

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

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

С https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html .

0
20.04.2020 10:23:14