Inicializar los campos de clase en el constructor o en la statement?

Recientemente he estado progtwigndo en C # y Java y tengo curiosidad sobre dónde está el mejor lugar para inicializar mis campos de clase.

¿Debo hacerlo en la statement ?:

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

¿o en un constructor ?:

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

Tengo mucha curiosidad por lo que algunos de ustedes, veteranos, piensan que es la mejor práctica. Quiero ser consistente y apegarme a un enfoque.

Mis reglas:

  1. No inicializar con los valores predeterminados en statement ( null , false , 0 , 0.0 …).
  2. Prefiere la inicialización en la statement si no tiene un parámetro constructor que cambie el valor del campo.
  3. Si el valor del campo cambia debido a un parámetro constructor ponga la inicialización en los constructores.
  4. Sea consistente en su práctica (la regla más importante).

En C # no importa. Las dos muestras de código que das son completamente equivalentes. En el primer ejemplo, el comstackdor de C # (¿o es el CLR?) Construirá un constructor vacío e inicializará las variables como si estuvieran en el constructor. Si ya hay un constructor, cualquier inicialización “arriba” se moverá a la parte superior.

En términos de las mejores prácticas, el primero es menos propenso a errores que el segundo, ya que alguien podría agregar fácilmente otro constructor y olvidarse de encadenarlo.

La semántica de C # difiere ligeramente de Java aquí. En C # la asignación en statement se realiza antes de llamar al constructor de la superclase. En Java, se realiza inmediatamente después de lo cual permite que se use “this” (particularmente útil para clases internas anónimas), y significa que la semántica de las dos formas realmente coincide.

Si puedes, completa los campos.

Creo que hay una advertencia. Una vez cometí tal error: en el interior de una clase derivada, traté de “inicializar en la statement” los campos heredados de una clase base abstracta. El resultado fue que existían dos conjuntos de campos, uno era “base” y el otro los recién declarados, y me costó bastante tiempo depurarlo.

La lección: para inicializar campos heredados , lo haría dentro del constructor.

Asumiendo el tipo en tu ejemplo, definitivamente prefieres inicializar los campos en el constructor. Los casos excepcionales son:

  • Campos en clases / métodos estáticos
  • Los campos escritos como static / final / et al

Siempre pienso en la lista de campos en la parte superior de una clase como la tabla de contenido (lo que está contenido aquí, no cómo se usa), y el constructor como introducción. Los métodos por supuesto son capítulos.

¿Qué pasa si te lo dije, depende?

En general, inicializo todo y lo hago de forma consistente. Sí, es demasiado explícito, pero también es un poco más fácil de mantener.

Si estamos preocupados por el rendimiento, entonces inicializo solo lo que se debe hacer y lo coloco en las áreas en las que más se aprovecha.

En un sistema de tiempo real, me pregunto si realmente necesito la variable o constante en absoluto.

Y en C ++, a menudo hago al lado de ninguna inicialización en cualquier lugar y lo muevo a una función Init (). ¿Por qué? Bueno, en C ++ si estás inicializando algo que puede generar una excepción durante la construcción del objeto, te abres a pérdidas de memoria.

En Java, un inicializador con la statement significa que el campo siempre se inicializa de la misma manera, independientemente de qué constructor se use (si tiene más de uno) o los parámetros de sus constructores (si tienen argumentos), aunque un constructor podría subsecuentemente cambie el valor (si no es final). Entonces, usar un inicializador con una statement sugiere a un lector que el valor inicializado es el valor que tiene el campo en todos los casos , independientemente de qué constructor se use e independientemente de los parámetros pasados ​​a cualquier constructor. Por lo tanto, use un inicializador con la statement solo si, y siempre si, el valor para todos los objetos construidos es el mismo.

Hay muchas y varias situaciones.

Solo necesito una lista vacía

La situación es clara. Solo necesito preparar mi lista y evitar que se arroje una excepción cuando alguien agrega un elemento a la lista.

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

Sé los valores

Sé exactamente qué valores quiero tener de forma predeterminada o necesito usar alguna otra lógica.

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

o

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

Lista vacía con valores posibles

A veces espero una lista vacía por defecto con la posibilidad de agregar valores a través de otro constructor.

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

Hay un pequeño beneficio de rendimiento al establecer el valor en la statement. Si lo configura en el constructor, en realidad se está configurando dos veces (primero en el valor predeterminado, luego reinicie en el ctor).

El diseño de C # sugiere que se prefiere la inicialización en línea, o no estaría en el idioma. Cada vez que puede evitar una referencia cruzada entre diferentes lugares en el código, en general estará mejor.

También existe la cuestión de la coherencia con la inicialización del campo estático, que debe estar en línea para obtener el mejor rendimiento. Las Pautas de diseño del marco para el diseño del constructor dicen esto:

✓ CONSIDERAR la inicialización de campos estáticos en línea en lugar de usar explícitamente constructores estáticos, porque el tiempo de ejecución puede optimizar el rendimiento de los tipos que no tienen un constructor estático definido explícitamente.

“Considerar” en este contexto significa hacerlo a menos que haya una buena razón para no hacerlo. En el caso de los campos de inicializador estático, una buena razón sería si la inicialización es demasiado compleja para ser codificada en línea.

Ser consecuente es importante, pero esta es la pregunta que debe hacerse: “¿Tengo un constructor para cualquier otra cosa?”

Normalmente, estoy creando modelos para transferencias de datos que la clase en sí misma no hace más que trabajar como vivienda para variables.

En estos escenarios, generalmente no tengo ningún método o constructor. Me parecería tonto crear un constructor con el exclusivo propósito de inicializar mis listas, especialmente porque puedo inicializarlas en línea con la statement.

Entonces, como muchos otros han dicho, depende de tu uso. Mantenlo simple, y no hagas nada extra que no tengas que hacer.

Considere la situación en la que tiene más de un constructor. ¿La inicialización será diferente para los diferentes constructores? Si serán iguales, ¿por qué repetir para cada constructor? Esto está en línea con la statement de kokos, pero puede no estar relacionado con los parámetros. Digamos, por ejemplo, que desea mantener una bandera que muestre cómo se creó el objeto. Entonces esa bandera se inicializaría de manera diferente para diferentes constructores, independientemente de los parámetros del constructor. Por otro lado, si repites la misma inicialización para cada constructor, dejas la posibilidad de que (involuntariamente) cambies el parámetro de inicialización en algunos de los constructores pero no en otros. Entonces, el concepto básico aquí es que el código común debe tener una ubicación común y no ser potencialmente repetido en diferentes ubicaciones. Entonces, siempre diría que lo incluyas en la statement hasta que tengas una situación específica en la que eso ya no funcione para ti.

Normalmente bash que el constructor no haga nada más que obtener las dependencias e inicializar los miembros de instancia relacionados con ellas. Esto te hará la vida más fácil si quieres probar tus clases por un grupo.

Si el valor que va a asignar a una variable de instancia no se ve influenciado por ninguno de los parámetros que le va a pasar al constructor, entonces, asígnelo en el momento de la statement.

No es una respuesta directa a su pregunta sobre la mejor práctica pero un punto de actualización importante y relacionado es que en el caso de una definición de clase genérica, déjelo en comstackdor para inicializar con valores predeterminados o tenemos que usar un método especial para inicializar campos a sus valores predeterminados (si eso es absolutamente necesario para la legibilidad del 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 } } 

Y el método especial para inicializar un campo genérico a su valor predeterminado es el siguiente:

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