Inicializar campos de class no construtor ou na declaração?

Eu tenho programado em C # e Java recentemente e estou curioso, onde o melhor lugar é inicializar meus campos de class.

Devo fazer isso na declaração ?:

public class Dice { private int topFace = 1; private Random myRand = new Random(); public void Roll() { // ...... } } 

ou em um construtor ?:

 public class Dice { private int topFace; private Random myRand; public Dice() { topFace = 1; myRand = new Random(); } public void Roll() { // ..... } } 

Estou realmente curioso sobre o que alguns de vocês veteranos acham que é a melhor prática. Eu quero ser consistente e manter uma abordagem.

Minhas regras:

  1. Não inicialize com os valores padrão na declaração ( null , false , 0 , 0.0 …).
  2. Prefira a boot na declaração se você não tiver um parâmetro de construtor que altere o valor do campo.
  3. Se o valor do campo mudar por causa de um parâmetro construtor, coloque a boot nos construtores.
  4. Seja consistente em sua prática (a regra mais importante).

Em c # não importa. As duas amostras de código que você fornece são totalmente equivalentes. No primeiro exemplo, o compilador C # (ou é o CLR?) Irá construir um construtor vazio e inicializar as variables ​​como se estivessem no construtor. Se já houver um construtor, qualquer boot “acima” será movida para o topo dele.

Em termos de melhores práticas, o primeiro é menos propenso a erros do que o segundo, pois alguém poderia facilmente adicionar outro construtor e esquecer de encadear o mesmo.

A semântica do C # difere um pouco do Java aqui. Em c # atribuição na declaração é realizada antes de chamar o construtor da superclass. Em Java, é feito imediatamente após o qual permite que ‘this’ seja usado (particularmente útil para classs internas anônimas), e significa que a semântica das duas formas realmente combina.

Se puder, torne os campos finais.

Eu acho que há uma ressalva. Uma vez cometi um erro desse tipo: Dentro de uma class derivada, tentei “inicializar na declaração” os campos herdados de uma class base abstrata. O resultado foi que existiam dois conjuntos de campos, um é “base” e outro é o recém-declarado, e me custou algum tempo para depurar.

A lição: para inicializar campos herdados , você faria isso dentro do construtor.

Assumindo o tipo em seu exemplo, definitivamente prefira inicializar campos no construtor. Os casos excepcionais são:

  • Campos em classs / methods estáticos
  • Campos typescripts como static / final / et al

Eu sempre penso na listview de campo no topo de uma class como o índice (o que está contido aqui, não como é usado), e o construtor como a introdução. Os methods, claro, são capítulos.

E se eu te dissesse, isso depende?

Eu em geral inicializo tudo e faço de forma consistente. Sim, é excessivamente explícito, mas também é um pouco mais fácil de manter.

Se estamos preocupados com o desempenho, então inicializo apenas o que precisa ser feito e o coloco nas áreas em que ele dá mais retorno.

Em um sistema de tempo real, questiono se preciso mesmo da variável ou da constante.

E em C ++ eu geralmente faço quase nenhuma boot em qualquer lugar e movo para uma function Init (). Por quê? Bem, em C ++, se você estiver inicializando algo que pode lançar uma exceção durante a construção de objects, você se abre para vazamentos de memory.

Em Java, um inicializador com a declaração significa que o campo é sempre inicializado da mesma maneira, independentemente de qual construtor é usado (se você tiver mais de um) ou dos parâmetros de seus construtores (se eles tiverem argumentos), embora um construtor possa subsequentemente altere o valor (se não for final). Portanto, usar um inicializador com uma declaração sugere a um leitor que o valor inicializado é o valor que o campo tem em todos os casos , independentemente de qual construtor é usado e independentemente dos parâmetros passados ​​a qualquer construtor. Portanto, use um inicializador com a declaração somente se, e sempre if, o valor para todos os objects construídos for o mesmo.

Existem muitas e várias situações.

Eu só preciso de uma lista vazia

A situação é clara. Eu só preciso preparar minha lista e evitar que uma exceção seja lançada quando alguém adiciona um item à lista.

 public class CsvFile { private List lines = new List(); public CsvFile() { } } 

Eu conheço os valores

Eu sei exatamente quais valores eu quero ter por padrão ou preciso usar alguma outra lógica.

 public class AdminTeam { private List usernames; public AdminTeam() { usernames = new List() {"usernameA", "usernameB"}; } } 

ou

 public class AdminTeam { private List usernames; public AdminTeam() { usernames = GetDefaultUsers(2); } } 

Lista vazia com valores possíveis

Às vezes eu espero uma lista vazia por padrão com a possibilidade de adicionar valores através de outro construtor.

 public class AdminTeam { private List usernames = new List(); public AdminTeam() { } public AdminTeam(List admins) { admins.ForEach(x => usernames.Add(x)); } } 

Existe um pequeno benefício no desempenho para definir o valor na declaração. Se você configurá-lo no construtor, ele está sendo definido duas vezes (primeiro para o valor padrão e, em seguida, redefinido no ctor).

O design do C # sugere que a boot inline é preferida ou não estaria na linguagem. Sempre que você puder evitar uma referência cruzada entre lugares diferentes no código, geralmente ficará melhor.

Há também a questão da consistência com a boot de campo estático, que precisa estar alinhada para um melhor desempenho. As Diretrizes de Design do Framework para o Constructor Design dizem o seguinte:

✓ CONSIDERAR a boot de campos estáticos inline em vez de usar explicitamente construtores estáticos, porque o tempo de execução é capaz de otimizar o desempenho de tipos que não têm um construtor estático explicitamente definido.

“Considere” neste contexto significa fazê-lo a menos que haja uma boa razão para não fazê-lo. No caso de campos estáticos de boot, um bom motivo seria se a boot fosse muito complexa para ser codificada em linha.

Ser consistente é importante, mas essa é a pergunta a se fazer: “Eu tenho um construtor para qualquer outra coisa?”

Normalmente, eu estou criando modelos para transferências de dados que a própria class não faz nada, exceto o trabalho como habitação para variables.

Nestes cenários, geralmente não tenho methods ou construtores. Seria tolo para mim criar um construtor com o propósito exclusivo de inicializar minhas listas, especialmente porque posso inicializá-las de acordo com a declaração.

Então, como muitos outros disseram, isso depende do seu uso. Mantê-lo simples e não faça nada extra que você não precisa.

Considere a situação em que você tem mais de um construtor. A boot será diferente para os diferentes construtores? Se eles forem iguais, por que repetir para cada construtor? Isso está de acordo com a instrução kokos, mas pode não estar relacionado a parâmetros. Por exemplo, digamos que você queira manter um sinalizador que mostre como o object foi criado. Em seguida, esse sinalizador seria inicializado de forma diferente para diferentes construtores, independentemente dos parâmetros do construtor. Por outro lado, se você repetir a mesma boot para cada construtor, deixará a possibilidade de alterar (inadvertidamente) o parâmetro de boot em alguns dos construtores, mas não em outros. Portanto, o conceito básico aqui é que o código comum deve ter um local comum e não ser potencialmente repetido em locais diferentes. Então, eu diria sempre colocá-lo na declaração até que você tenha uma situação específica em que isso não funcione mais para você.

Eu normalmente tento o construtor para fazer nada além de obter as dependencies e inicializar os membros da instância relacionada com eles. Isso tornará sua vida mais fácil se você quiser testar suas classs.

Se o valor que você vai atribuir a uma variável de instância não for influenciado por nenhum dos parâmetros que você irá passar para o seu construtor, atribua-o no momento da declaração.

Não é uma resposta direta à sua pergunta sobre a melhor prática, mas um ponto de atualização importante e relacionado é que no caso de uma definição de class genérica, deixe-a no compilador para inicializar com valores padrão ou temos que usar um método especial para inicializar campos aos seus valores padrão (se isso for absolutamente necessário para a legibilidade do código).

 class MyGeneric { 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 } } 

E o método especial para inicializar um campo genérico para seu valor padrão é o seguinte:

 class MyGeneric { T data = default(T); public MyGeneric() { // The same method can be used here in constructor } }