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

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

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

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

yoyo 11.07.2014 03:45

Поскольку вы упомянули "лучшие практики": Satisfice.com/blog/archives/5164, forbes.com/sites/mikemyatt/2012/08/15/best-practices-arent/…

Stephen C 21.02.2020 05:27
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
431
2
141 401
15
Перейти к ответу Данный вопрос помечен как решенный

Ответы 15

Ответ принят как подходящий

Мои правила:

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

Я не совсем понимаю 1. Вы имеете в виду значение типа по умолчанию или значение, которое вы хотите использовать по умолчанию. Скажем, я объявляю int, который по умолчанию должен быть 5, я должен делать это не в объявлении, а в конструкторе?

Didier A. 11.09.2012 23:54

Я ожидаю, что kokos означает, что вы не должны инициализировать элементы их значениями по умолчанию (0, false, null и т. д.), Поскольку компилятор сделает это за вас (1.). Но если вы хотите инициализировать поле чем-нибудь, кроме значения по умолчанию, вы должны сделать это в объявлении (2.). Я думаю, что вас смущает использование слова «по умолчанию».

Ricky Helgesson 11.10.2012 21:12

Я не согласен с правилом 1 - не указывая значение по умолчанию (независимо от того, инициализировано ли оно компилятором или нет), вы оставляете разработчика Угадай или ищите документацию о том, каким будет значение по умолчанию для этого конкретного языка. Для удобства чтения я всегда указываю значение по умолчанию.

James 09.01.2013 16:50

Значение по умолчанию для типа default(T) - это всегда значение, которое имеет внутреннее двоичное представление 0.

Olivier Jacot-Descombes 13.03.2013 23:26

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

Cemafor 26.04.2013 22:54

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

supercat 03.07.2014 00:42

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

yoyo 11.07.2014 03:39

Я хочу инициализировать поле и получить значение из Castle, я должен сделать это в объявлении или конструкторе или в файле конфигурации замка?

user1785960 30.05.2016 11:31

@kokos Согласно вашему правилу 1, нужно ли нам инициализировать значения по умолчанию в конструкторе вместо объявления или действительно необходимо инициализировать их значениями по умолчанию, поскольку они являются переменными экземпляра

Kasun Siyambalapitiya 21.07.2016 09:57

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

Patrick Szalapski 18.10.2016 19:55

Я не согласен с теми, кто не согласен с правилом 1. Можно ожидать, что другие выучат язык C#. Так же, как мы не комментируем каждый цикл foreach словами «это повторяет следующее для всех элементов в списке», нам не нужно постоянно пересматривать значения C# по умолчанию. Нам также не нужно делать вид, что C# имеет неинициализированную семантику. Поскольку отсутствие значения имеет четкое значение, его можно не указывать. Если в идеале быть явным, вы всегда должны использовать new при создании новых делегатов (как это требовалось в C# 1). Но кто это делает? Язык предназначен для сознательных программистов.

Edward Brey 14.05.2017 08:24

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

IEnjoyEatingVegetables 29.10.2017 05:14

@EdwardBrey Да, я согласен и хотел бы, чтобы я прочитал ваше, прежде чем писать свое.

IEnjoyEatingVegetables 29.10.2017 05:15

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

James 30.10.2017 11:25

@PatrickSzalapski Я не согласен с этим. Компьютерный код - это буквально просто инструкции для компьютера, поэтому ни в коем случае не случайно, что компьютеры выполняет код компьютер. Компьютерный код служит интерфейсом между разработчиком и компьютером, поэтому приоритет удобочитаемости и производительности является субъективным. Я бы очень поспорил с тем, что хороший, опытный разработчик выучит язык, и ему не нужно использовать «читабельность» в качестве опоры при программировании, особенно, когда это делает жертву производительностью; Я хочу, чтобы мой компьютер хорошо справлялся с задачами, о которых я ему говорю.

Kröw 25.06.2019 13:21

@EdwardBrey Это хороший аргумент, но я лично считаю, что foreach без комментария все еще читается / очищается, а «неинициализированная» переменная - нет - мне не нужно проверять документацию C#, чтобы вспомнить, как работает цикл foreach, но Мне нужно будет проверить документацию, чтобы вспомнить, какие значения по умолчанию используются для типа.

Ruben9922 24.10.2019 14:33

@ Ruben9922 Не нужно много посещать документацию, чтобы правило «все биты-ноль» стало такой же второй натурой, как foreach.

Edward Brey 24.10.2019 14:45

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

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

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

Почему «определенно» так? Вы предоставили только предпочтение стиля, без объяснения причин. Ой, подождите, неважно, согласно отвечать @quibblesome, они «совершенно эквивалентны», так что на самом деле это просто личное предпочтение стиля.

RayLuo 04.05.2017 02:45

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

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

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

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

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

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

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

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

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

Это неверно, если вы решите инициализировать класс с помощью GetUninitializedObject. Все, что находится в ctor, не будет затронуто, но объявления полей будут запущены.

Wolf5 03.01.2013 13:18

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

Jon Skeet 13.08.2013 12:52

Действительно? Клянусь, я взял эту информацию из среды CLR Рихтера через C# (2-е издание, я думаю), и суть заключалась в том, что это был синтаксический сахар (возможно, я прочитал это неправильно?), А CLR просто вставила переменные в конструктор. Но вы заявляете, что это не так, т.е. инициализация члена может срабатывать до инициализации ctor в безумном сценарии вызова виртуального базового ctor и переопределения в рассматриваемом классе. Правильно ли я понял? Вы только что это узнали? Озадаченный этим свежим комментарием к посту 5-летней давности (OMG, это было 5 лет?).

Quibblesome 13.08.2013 23:18

@Quibblesome: конструктор дочернего класса будет содержать связанный вызов родительского конструктора. Компилятор языка может включать столько или меньше кода перед этим, сколько пожелает, при условии, что родительский конструктор вызывается ровно один раз на всех путях кода и ограниченное использование объекта, находящегося в стадии конструирования, перед этим вызовом. Одно из моих неудобств в C# заключается в том, что, хотя он может генерировать код, который инициализирует поля, и код, использующий параметры конструктора, он не предлагает механизма для инициализации полей на основе параметров конструктора.

supercat 21.02.2015 00:02

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

Pranesh Janarthanan 23.12.2019 18:50

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

В C# для полей всегда сначала устанавливается значение по умолчанию. Наличие инициализатора не имеет значения.

Jeffrey L Whitledge 10.05.2010 22:20

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

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

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

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

Итак, если вы ссылаетесь на поле по derivedObject.InheritedField, относится ли оно к вашему базовому или производному?

RayLuo 04.05.2017 02:39

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

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

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

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));
    }
}

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

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

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

✓ CONSIDER initializing static fields inline rather than explicitly using static constructors, because the runtime is able to optimize the performance of types that don’t have an explicitly defined static constructor.

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

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

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

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

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

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

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

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
    }
}

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

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

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

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

This works well when the initialization value is available and the initialization can be put on one line. However, this form of initialization has limitations because of its simplicity. If initialization requires some logic (for example, error handling or a for loop to fill a complex array), simple assignment is inadequate. Instance variables can be initialized in constructors, where error handling or other logic can be used.

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

Другие вопросы по теме