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


174

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

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



77
@ Frustrado No puede marcar una pregunta como un duplicado de algo en otro sitio, aunque ciertamente está relacionado.
Adam Lear

@ Anna Lear: Lo sé, voté por migrar para no cerrar como duplicado. Una vez que se migra, se puede cerrar como un duplicado correctamente .
FrustratedWithFormsDesigner

55
@ Frustrado No creo que deba migrarse.
Adam Lear

15
Esta parece una pregunta mucho más adecuada para P.SE vs. SO. Por eso lo pregunté aquí.
RationalGeek

Respuestas:


169

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, usa una clase.

C # es bueno porque las estructuras y clases no tienen diferencias explícitas en la declaración que no sea la palabra clave definitoria; por lo tanto, si siente que necesita "actualizar" una estructura a una clase, o viceversa "degradar" una clase a una estructura, es principalmente una cuestión de cambiar la palabra clave (hay algunas otras trampas; las estructuras no pueden derivar de cualquier otra clase o tipo de estructura, y no pueden definir explícitamente un constructor sin parámetros predeterminado).

Digo "sobre todo", porque lo más importante que se debe saber acerca de las estructuras es que, dado que son tipos de valor, tratarlas como clases (tipos de referencia) puede terminar siendo 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. Instancia 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 vuelta 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, hazlo una estructura. Las propiedades aún son 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 su uso por el método, y luego se destruye cuando el método termina dejando la instancia inicial intacta.

Esto se vuelve aún más confuso si tuviera un tipo de referencia como miembro de su estructura (no está prohibido, pero es una práctica extremadamente mala en prácticamente todos los casos); la clase no se clonaría (solo la referencia de la estructura a la misma), por lo que los cambios en la estructura no afectarían al objeto original, pero los cambios en la subclase de la estructura AVERÍAN la instancia del código de llamada. Esto puede poner fácilmente 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 en C # dicen que siempre haga 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 quiere 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 los obliga a tratar una sola instancia de su estructura como un "valor" conceptual, indivisible y distinto (pero posiblemente equiparable) de todos los demás. Si realizan una operación en un "valor" almacenado por su tipo, obtienen un nuevo "valor" que es diferente de su valor inicial,

Para un buen ejemplo, mira el tipo 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 una fecha y hora son un "valor", como el número 5, y un cambio en el número 5 da como resultado un nuevo valor que no es 5. Solo porque 5 + 1 = 6 no significa que 5 es ahora 6 porque le agregaste 1. DateTimes funciona de la misma manera; 12:00 no se "convierte" en 12:01 si agrega un minuto, en su lugar obtiene un nuevo valor 12:01 que es distinto de 12:00. Si este es un estado de cosas lógico para su tipo (buenos ejemplos conceptuales que no están integrados en .NET son Money, Distance, Weight y otras cantidades de una UOM donde las operaciones deben tener en cuenta todas las partes del valor) luego use 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.


1
¡Buena respuesta! Diría que leerlo es la metodología y el libro 'Desarrollo impulsado por dominio', que parece estar relacionado con su opinión sobre por qué uno debería usar clases o estructuras basadas en el concepto que realmente está tratando de implementar
Maxim Popravko

1
Solo un pequeño detalle que vale la pena mencionar: no puede definir su propio constructor predeterminado en una estructura. Tienes que conformarte con el que inicializa todo a su valor predeterminado. Por lo general, eso es bastante menor, especialmente en clase que son buenos candidatos para ser una estructura. Pero significará que cambiar una clase a una estructura puede implicar más que simplemente cambiar una palabra clave.
Laurent Bourgault-Roy

1
@ LaurentBourgault-Roy - Cierto. También hay otras trampas; Las estructuras no pueden tener una jerarquía de herencia, por ejemplo. Pueden implementar interfaces, pero no pueden derivarse de otra cosa que System.ValueType (implícitamente por ser estructuras).
KeithS

Como desarrollador de JavaScript, tengo que preguntar dos cosas. ¿En qué se diferencian los literales de objeto de los usos típicos de estructuras y por qué es importante que las estructuras sean inmutables?
Erik Reppen

1
@ErikReppen: en respuesta a sus dos preguntas: cuando una estructura se pasa a una función, se pasa por valor. Con una clase, está pasando una referencia a la clase (aún por valor). Eric Lippert analiza por qué esto es un problema: blogs.msdn.com/b/ericlippert/archive/2008/05/14/… . El último párrafo de KeithS también cubre estas preguntas.
Brian

14

La respuesta: "Usar una estructura para construcciones de datos puros y una clase para objetos con operaciones" es definitivamente una OMI incorrecta. Si una estructura tiene una gran cantidad de propiedades, entonces una clase es casi siempre más apropiada. Microsoft a menudo dice, desde un punto de vista de eficiencia, si su tipo es mayor de 16 bytes, debería ser una clase.

https://stackoverflow.com/questions/1082311/why-should-a-net-struct-be-less-than-16-bytes


1
Absolutamente. Votaría si pudiera. No entiendo de dónde vino la idea de que las estructuras son para datos y los objetos no. Una estructura en C # es solo un mecanismo para asignar en la pila. Se pasan copias de toda la estructura en lugar de solo una copia del puntero de un objeto.
mike30

2
@mike: las estructuras de C # no se asignan necesariamente en la pila.
Lee

1
@mike La idea de "estructuras son para datos" probablemente proviene del hábito adquirido por muchos años de programación en C. C no tiene clases y sus estructuras son contenedores de solo datos.
Konamiman

Obviamente, hay al menos 3 programadores de C (votaron) que leyeron la respuesta de Michael K ;-)
bytedev

10

La diferencia más importante entre ay classa structes lo que sucede en la siguiente situación:

  Cosa thing1 = nueva Cosa ();
  thing1.somePropertyOrField = 5;
  Cosa thing2 = thing1;
  thing2.somePropertyOrField = 9;

¿Cuál debería ser el efecto de la última declaración thing1.somePropertyOrField? Si Thinguna estructura, y somePropertyOrFieldes un campo público expuesto, los objetos thing1y thing2serán "separados" entre sí, por lo que la última declaración no afectará thing1. Si Thinges una clase, entonces thing1y se thing2unirán entre sí, por lo que se escribirá la última declaración 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 última semántica tuviera 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 propósito de mantener algunos datos va a ser mutable, y Si no será obvio si las instancias están unidas a algo, la cosa debería ser una estructura (probablemente con campos expuestos) para dejar en claro que las instancias no están unidas a nada más.

Por ejemplo, considere las declaraciones:

  Persona somePerson = myPeople.GetPerson ("123-45-6789");
  somePerson.Name = "Mary Johnson"; // Había sido "Mary Smith"

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

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 estructuras "inmutables" (donde los invariantes se imponen en un constructor) pero requieren que una estructura completa se reescriba cada vez que cualquier parte de ella cambie es incómoda, derrochadora y más propensa a causar errores de lo que simplemente expondría campos directamente. Por ejemplo, considere una PhoneNumberestructura cuyos campos, incluir entre otros, AreaCodey Exchange, y suponga que uno tiene un List<PhoneNumber>. El efecto de lo siguiente debería ser bastante claro:

  para (int i = 0; i <myList.Count; i ++)
  {
    PhoneNumber theNumber = myList [i];
    if (theNumber.AreaCode == "312")
    {
      cadena 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 sabe o no le importa ningún campo que PhoneNumberno sea AreaCodey Exchange. Si PhoneNumberfuera una estructura llamada "inmutable", sería necesario que proporcionara un withXXmétodo para cada campo, 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 para código como el anterior para saber acerca de cada campo en la estructura. No exactamente atractivo.

Por cierto, hay al menos dos casos en los que las estructuras pueden contener 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 como un atajo para mantener las propiedades del objeto. Por ejemplo, un `KeyValuePair` mantendría las identidades de ciertos botones, pero no se esperaría que contuviera una información persistente sobre las posiciones de esos botones, los estados resaltados, etc.
  2. La estructura sabe que tiene la única referencia a ese objeto, nadie más obtendrá una referencia, y cualquier mutación que alguna vez se realice a ese objeto se habrá realizado antes de que se almacene una referencia a él en cualquier lugar.

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


7

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

RECURSOS ADICIONALES

Para una explicación más formal, MSDN dice: http://msdn.microsoft.com/en-us/library/ms229017.aspx Este enlace verifica lo que mencioné anteriormente, las estructuras solo deben usarse para alojar una pequeña cantidad de datos.


55
Las preguntas en otro sitio (incluso si ese sitio es Stack Overflow ) no cuentan como duplicados. Expanda su respuesta para incluir una respuesta real y no solo enlaces a otros lugares. Si los enlaces alguna vez cambian, su respuesta tal como está escrita ahora será inútil y nos gusta preservar la información y hacer que los Programadores sean lo más autónomos posible. ¡Gracias!
Adam Lear

2
@Anna Lear Gracias por hacérmelo saber, lo tendré en cuenta de ahora en adelante.
rrazd

2
-1 "Tiendo a usar estructuras solo cuando se necesita un método, por lo que una clase parece demasiado" pesada "". ... ¿pesado? ¿Qué significa eso realmente? ... y realmente estás diciendo que solo porque hay un método, entonces probablemente debería ser una estructura?
bytedev

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 get / set, use una estructura. Esto hace 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 puede hacer mucho Un avión podría tener múltiples operaciones que lo mutan de alguna manera, como mover las superficies de control y cambiar el empuje del motor. Sería mejor como clase.


2
"Usar una estructura para construcciones de datos puros y una clase para objetos con operaciones" en C # no tiene sentido. Vea mi respuesta a continuación.
bytedev

1
"Utilice una estructura para construcciones de datos puros y una clase para objetos con operaciones". Esto es cierto para C / C ++, no C #
taktak004
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.