¿Cuándo usa una estructura en lugar de una clase? [cerrado]

158

¿Cuáles son sus reglas generales para cuándo usar las estructuras vs. clases? Estoy pensando en la definición de C # de esos términos, pero si su idioma tiene conceptos similares, también me gustaría escuchar su opinión.

Tiendo a usar las clases para casi todo, y uso las estructuras solo cuando algo es muy simplista y debería ser un tipo de valor, como PhoneNumber o algo así. Pero esto parece ser un uso relativamente menor y espero que haya más casos de uso interesantes.

    
pregunta RationalGeek 12.07.2011 - 18:22
fuente

5 respuestas

150

La regla general a seguir es que las estructuras deben ser colecciones pequeñas, simples (de un nivel) de propiedades relacionadas, que son inmutables una vez creadas; para cualquier otra cosa, use una clase.

C # es bueno en que las estructuras y las clases no tienen diferencias explícitas en la declaración aparte de la palabra clave que define; Por lo tanto, si siente que necesita "actualizar" una estructura a una clase o, a la inversa, "degradar" una clase a una estructura, es principalmente una simple cuestión de cambiar la palabra clave (hay algunos otros problemas; las estructuras no pueden derivarse de cualquier otra clase o tipo de estructura, y no pueden definir explícitamente un constructor sin parámetros predeterminado).

Digo "en su mayoría", porque lo más importante que se debe saber acerca de las estructuras es que, debido a que son tipos de valor, tratarlas como clases (tipos de referencia) puede terminar con un dolor y medio. Particularmente, hacer que las propiedades de una estructura sean mutables puede causar un comportamiento inesperado.

Por ejemplo, supongamos que tiene una clase SimpleClass con dos propiedades, A y B. Usted crea una copia de esta clase, inicializa A y B, y luego pasa la instancia a otro método. Ese método modifica aún más A y B. De nuevo en la función de llamada (la que creó la instancia), A y B de su instancia tendrán los valores que les da el método llamado.

Ahora, lo haces una estructura. Las propiedades siguen siendo mutables. Realiza las mismas operaciones con la misma sintaxis que antes, pero ahora, los nuevos valores de A y B no están en la instancia después de llamar al método. ¿Que pasó? Bueno, tu clase ahora es una estructura, lo que significa que es un tipo de valor. Si pasa un tipo de valor a un método, el valor predeterminado (sin una palabra clave out o ref) es pasar "por valor"; se crea una copia superficial de la instancia para que la utilice el método, y luego se destruye cuando el método se realiza dejando intacta la instancia inicial.

Esto se vuelve aún más confuso si tuviera un tipo de referencia como miembro de su estructura (no rechazada, pero extremadamente mala práctica en prácticamente todos los casos); la clase no se clonaría (solo la referencia de la estructura a ella), por lo que los cambios en la estructura no afectarán al objeto original, pero los cambios en la subclase de la estructura Afectarán la instancia desde el código que llama. Esto puede fácilmente poner estructuras mutables en estados muy inconsistentes que pueden causar errores muy lejos de donde está el problema real.

Por esta razón, prácticamente todas las autoridades de C # dicen que siempre deben hacer que sus estructuras sean inmutables; permitir al consumidor especificar los valores de las propiedades solo en la construcción de un objeto, y nunca proporcionar ningún medio para cambiar los valores de esa instancia. Los campos de solo lectura, o propiedades de solo obtención, son la regla. Si el consumidor desea cambiar el valor, puede crear un nuevo objeto basado en los valores del anterior, con los cambios que desee, o puede llamar a un método que hará lo mismo. Esto les obliga a tratar una sola instancia de su estructura como un "valor" conceptual, indivisible y distinto de (pero posiblemente equiparable a) todos los demás. Si realizan una operación en un "valor" almacenado por su tipo, obtendrán un nuevo "valor" que es diferente de su valor inicial, pero aún así comparable y / o semánticamente compatible.

Para un buen ejemplo, mira el tipo de DateTime. No puede asignar ninguno de los campos de una instancia de DateTime directamente; debe crear uno nuevo o llamar a un método en el existente que producirá una nueva instancia. Esto se debe a que la fecha y la hora son un "valor", como el número 5, y un cambio al número 5 da como resultado un nuevo valor que no es 5. Solo porque 5 + 1 = 6 no significa que 5 sea ahora 6 porque le has añadido 1. DateTimes funciona de la misma manera; 12:00 no se "convierte" en 12:01 si agrega un minuto, en lugar de eso, obtiene un nuevo valor 12:01 que es distinto de 12:00. Si esta es una situación lógica para su tipo (los buenos ejemplos conceptuales que no están integrados en .NET son dinero, distancia, peso y otras cantidades de una UOM donde las operaciones deben tener en cuenta todas las partes del valor), A continuación, utilice una estructura y diseñe en consecuencia. En la mayoría de los otros casos en los que los subelementos de un objeto deben ser mutables independientemente, use una clase.

    
respondido por el KeithS 12.07.2011 - 19:16
fuente
11

La respuesta: "Use una estructura para construcciones de datos puros y una clase para objetos con operaciones" es IMO definitivamente errónea. Si una estructura contiene una gran cantidad de propiedades, entonces una clase es casi siempre más apropiada. Microsoft a menudo dice que, desde un punto de vista de eficiencia, si su tipo es más grande que 16 bytes, debería ser una clase.

enlace

    
respondido por el bytedev 11.12.2012 - 11:20
fuente
9

La diferencia más importante entre un class y un struct es lo que sucede en la siguiente situación:

  Thing thing1 = new Thing();
  thing1.somePropertyOrField = 5;
  Thing thing2 = thing1;
  thing2.somePropertyOrField = 9;

¿Cuál debería ser el efecto de la última declaración en thing1.somePropertyOrField ? Si Thing a struct, y somePropertyOrField es un campo público expuesto, los objetos thing1 y thing2 serán "separados" entre sí, por lo que la última declaración no afectará a thing1 . Si Thing es una clase, entonces thing1 y thing2 se adjuntarán entre sí, por lo que la última instrucción escribirá thing1.somePropertyOrField . Uno debería usar una estructura en los casos en que la semántica anterior tendría más sentido, y debería usar una clase en los casos en que la semántica anterior tendría más sentido.

Tenga en cuenta que si bien algunas personas aconsejan que el deseo de hacer algo mutable es un argumento a favor de que sea la clase, sugeriría lo contrario: si algo que existe con el fin de mantener algunos datos será mutable, y si no es obvio si las instancias están vinculadas a algo, la cosa debería ser una estructura (probablemente con campos expuestos) para aclarar que las instancias no están vinculadas a ninguna otra cosa.

Por ejemplo, considera las afirmaciones:

  Person somePerson = myPeople.GetPerson("123-45-6789");
  somePerson.Name = "Mary Johnson"; // Had been "Mary Smith"

¿La segunda declaración modificará la información almacenada en myPeople ? Si Person es una estructura de campo expuesto, no lo hará, y el hecho de que no será una consecuencia obvia de que sea una estructura de campo expuesto ; Si Person es una estructura y uno desea actualizar myPeople , claramente tendría que hacer algo como myPeople.UpdatePerson("123-45-6789", somePerson) . Sin embargo, si Person es una clase, puede ser mucho más difícil determinar si el código anterior nunca actualizaría el contenido de MyPeople , siempre lo actualizaría o, a veces, lo actualizaría.

Con respecto a la noción de que las estructuras deben ser "inmutables", no estoy de acuerdo en general. Hay casos de uso válidos para las estructuras "inmutables" (donde las invariantes se imponen en un constructor), pero requieren que una estructura completa se vuelva a escribir cada vez que cualquier parte de los cambios sea torpe, derrochadora y más apta para causar errores de lo que simplemente sería exponer campos directamente Por ejemplo, considere una estructura PhoneNumber cuyos campos incluyen, entre otros, AreaCode y Exchange , y suponga que uno tiene un List<PhoneNumber> . El efecto de lo siguiente debería ser bastante claro:

  for (int i=0; i < myList.Count; i++)
  {
    PhoneNumber theNumber = myList[i];
    if (theNumber.AreaCode == "312")
    {
      string newExchange = "";
      if (new312to708Exchanges.TryGetValue(theNumber.Exchange), out newExchange)
      {
        theNumber.AreaCode = "708";
        theNumber.Exchange = newExchange;
        myList[i] = theNumber;
      }
    }
  }

Tenga en cuenta que nada en el código anterior conoce o se preocupa por ningún campo en PhoneNumber que no sea AreaCode y Exchange . Si PhoneNumber fuera una estructura llamada "inmutable", sería necesario que proporcionara un método withXX para cada campo, lo que devolvería una nueva instancia de estructura que contenía el valor pasado en el campo indicado. o de lo contrario sería necesario que un código como el anterior conozca cada campo en la estructura. No es exactamente atractivo.

Por cierto, hay al menos dos casos en que las estructuras pueden contener razonablemente referencias a tipos mutables.

  1. La semántica de la estructura indica que está almacenando la identidad del objeto en cuestión, en lugar de un acceso directo para mantener las propiedades del objeto. Por ejemplo, un 'KeyValuePair' mantendría las identidades de ciertos botones, pero no se esperaría que mantuviera una información persistente sobre las posiciones de esos botones, estados de resaltado, etc.
  2. La estructura sabe que contiene la única referencia a ese objeto, nadie más obtendrá una referencia, y cualquier mutación que se realice en ese objeto se habrá realizado antes de que se almacene una referencia a él en cualquier lugar.

En el escenario anterior, la IDENTIDAD del objeto será inmutable; en el segundo, la clase del objeto anidado no puede imponer la inmutabilidad, pero la estructura que contiene la referencia lo hará.

    
respondido por el supercat 06.07.2012 - 23:59
fuente
6

Tiendo a usar estructuras solo cuando se necesita un método, por lo que hacer que una clase parezca demasiado "pesada". Así, a veces, trato a las estructuras como objetos ligeros. CUIDADO: esto no siempre funciona y no siempre es lo mejor, por lo que realmente depende de la situación.

RECURSOS EXTRA

Para una explicación más formal, MSDN dice: enlace Este enlace verifica lo que mencioné anteriormente, las estructuras solo deben usarse para albergar una pequeña cantidad de datos.

    
respondido por el rrazd 12.07.2011 - 18:25
fuente
2

Use una estructura para construcciones de datos puros y una clase para objetos con operaciones.

Si su estructura de datos no necesita control de acceso y no tiene operaciones especiales que no sean obtener / configurar, use una estructura. Esto hace que sea obvio que toda esa estructura es un contenedor de datos.

Si su estructura tiene operaciones que modifican la estructura de una manera más compleja, use una clase. Una clase también puede interactuar con otras clases a través de argumentos a métodos.

Piense en ello como la diferencia entre un ladrillo y un avión. Un ladrillo es una estructura; Tiene longitud, anchura y altura. No se puede hacer mucho. Un avión podría tener múltiples operaciones que lo muten de alguna manera, como mover las superficies de control y cambiar el empuje del motor. Sería mejor como clase.

    
respondido por el Michael K 12.07.2011 - 18:49
fuente

Lea otras preguntas en las etiquetas