2008-08-23 14 views
311

He estado programando en C# y Java recientemente y tengo curiosidad sobre dónde está el mejor lugar para inicializar mis campos de clase.Inicializar campos de clase en el constructor o en la declaración?

¿Debo hacerlo en la declaración ?:

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() 
    { 
     // ..... 
    } 
} 

Estoy muy curioso lo que algunos de ustedes los veteranos que es la mejor práctica. Quiero ser consistente y apegarme a un enfoque.

+0

Tenga en cuenta que para las estructuras no puede tener inicializadores de campo de instancia, por lo que no tiene más remedio que usar el constructor. – yoyo

Respuesta

227

Mis reglas:

  1. no inicializan con los valores por defecto en la declaración (null, false, 0, 0.0 ...).
  2. Prefiere la inicialización en la declaración si no tiene un parámetro de 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).
+0

Realmente no entiendo 1. ¿Quiere decir el valor predeterminado del tipo, o el valor que desea como predeterminado? Digamos que declaro un int cuyo valor por defecto debería ser 5. No debería hacerlo en declaración, sino en el constructor. –

+2

Espero que kokos signifique que no debe inicializar los miembros a sus valores predeterminados (0, falso, nulo, etc.), ya que el compilador lo hará por usted (1.). Pero si desea inicializar un campo a cualquier valor que no sea su valor predeterminado, debe hacerlo en la declaración (2.). Creo que podría ser el uso de la palabra "predeterminado" lo que te confunde. –

+74

No estoy de acuerdo con la regla 1: al no especificar un valor predeterminado (independientemente de si el compilador lo inicializó o no), usted está dejando que el desarrollador * adivine * o busque la documentación sobre cuál sería el valor predeterminado para ese idioma en particular ser. Para fines de legibilidad, siempre especificaría el valor predeterminado. – James

5

Suponiendo que el tipo en su ejemplo, definitivamente prefiere inicializar los campos en el constructor. Los casos excepcionales son:

  • campos en clases/métodos estáticos
  • campos escribirse como estática/FINAL/et al

Siempre pienso en la lista de campo en la parte superior de una clase como el 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.

+0

¿Por qué es "definitivamente" entonces? Usted proporcionó solo una preferencia de estilo, sin explicar su razonamiento. Oh, espera, no importa, de acuerdo con @quibblesome [respuesta] (http://stackoverflow.com/a/25130/728675), son "totalmente equivalentes", por lo que es realmente una preferencia de estilo personal. – RayLuo

3

¿Qué sucede si te digo que 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 tiene que hacerse y lo coloco en las áreas en las que da más por el dinero.

En un sistema de tiempo real, me pregunto si incluso 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.

0

Normalmente intento 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 va a pasar al constructor, entonces asígnelo en el momento de la declaración.

127

En C# no importa. Las dos muestras de código que das son completamente equivalentes. En el primer ejemplo, el compilador 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 la mejor práctica, la primera es menos propensa a errores que la segunda, ya que alguien podría agregar fácilmente otro constructor y olvidarse de encadenarla.

+66

+1 el argumento de encadenamiento del constructor es fuerte – annakata

+4

Eso no es correcto si elige inicializar la clase con GetUninitializedObject. Lo que está en el ctor no se tocará, pero las declaraciones de campo se ejecutarán. – Wolf5

+19

En realidad, sí importa. Si el constructor de la clase base llama a un método virtual (que generalmente es una mala idea, pero puede suceder) que se invalida en la clase derivada, utilizando inicializadores de variables de instancia la variable se inicializará cuando se llame al método, mientras que usando la inicialización en el constructor, no lo serán. (Los inicializadores de variables de instancia se ejecutan * antes de que se llame al constructor de la clase base). –

1

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

+2

En C#, los campos siempre se establecen primero en el valor predeterminado. La presencia de un inicializador no hace diferencia. –

13

La semántica de C# difiere ligeramente de Java aquí. En C# la asignación en declaración 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 puede, haga que los campos sean definitivos.

11

Creo que hay una advertencia. Una vez cometí tal error: en el interior de una clase derivada, traté de "inicializar en la declaración" 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.

+0

Entonces, si refiere el campo por 'derivedObject.InheritedField', ¿se está refiriendo a su base uno o al derivado? – RayLuo

2

Hay muchas situaciones diferentes.

Sólo 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<CsvRow> lines = new List<CsvRow>(); 

    public CsvFile() 
    { 
    } 
} 

sé los valores

Yo sé exactamente cuáles son los valores que quiero tener de manera predeterminada o tengo que usar alguna otra lógica.

public class AdminTeam 
{ 
    private List<string> usernames; 

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

o

public class AdminTeam 
{ 
    private List<string> usernames; 

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

lista vacía con posibles valores

veces me esperaba una lista vacía de forma predeterminada con la posibilidad de añadir valores a través de otro constructor.

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

    public AdminTeam() 
    { 
    } 

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

En Java, un inicializador con la declaración significa que el campo siempre está inicializado la misma manera, independientemente del constructor se utiliza (si tiene más de uno) o los parámetros de sus constructores (si tienen argumentos), aunque un constructor puede cambiar posteriormente el valor (si no es definitivo).Entonces, usar un inicializador con una declaración sugiere a un lector que el valor inicializado es el valor que el campo tiene 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 declaración solo si, y siempre si, el valor para todos los objetos construidos es el mismo.

1

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, generalmente 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 un mejor rendimiento. El diseño Directrices marco para Constructor Design decir esto:

✓ 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.

"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.

1

Ser consistente 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 alojamiento 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 declaración.

Como muchos otros han dicho, depende de su uso. Mantenlo simple, y no hagas nada extra que no tengas que hacer.

0

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 declaración 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 declaración hasta que tengas una situación específica en la que eso ya no funcione para ti.

Cuestiones relacionadas