2008-09-17 17 views
271

MSDN dice que debe usar estructuras cuando necesite objetos livianos. ¿Hay otros escenarios cuando una estructura es preferible a una clase?¿Cuándo debería usar una estructura en lugar de una clase?

Editar:
Algunas personas han olvidado que: 1.
estructuras pueden tener métodos!
2. las estructuras no tienen capacidades de herencia.

Otra edición:
entiendo las diferencias técnicas, sólo que no tengo una buena sensación para CUANDO de usar una estructura.

+0

Solo un recordatorio: lo que la mayoría de la gente tiende a olvidar en este contexto es que en C# las estructuras también pueden tener métodos. –

Respuesta

280

MSDN tiene la respuesta: Choosing Between Classes and Structures.

Básicamente, esa página le da una lista de verificación de 4 elementos y dice que use una clase a menos que su tipo cumpla con todos los criterios.

No definir una estructura a menos que el tipo tiene todas las siguientes características:

  • Es lógicamente representa un único valor, similar a los tipos primitivos (entero, doble, y así sucesivamente) .
  • Tiene un tamaño de instancia inferior a 16 bytes.
  • Es inmutable.
  • No tendrá que estar en la caja con frecuencia.
+0

Tal vez me falta algo obvio, pero no entiendo muy bien el razonamiento detrás de la parte "inmutable". ¿Por qué es esto necesario? ¿Alguien podría explicarlo? –

+9

Supongo que es otra forma de decir "tiene semántica de valores" – Trap

+1

Probablemente lo hayan recomendado porque si la estructura es inmutable, no importará que tenga semántica de valores en lugar de semántica de referencia. La distinción solo importa si muta el objeto/estructura después de hacer una copia. –

2

cuando realmente no necesita el comportamiento, pero se necesita más estructura que un simple matriz o diccionario.

Seguimiento Así es como pienso en las estructuras en general. Sé que pueden tener métodos, pero me gusta mantener esa distinción mental general.

+0

¿Por qué dices eso? Las estructuras pueden tener métodos. –

2

Como dijo @Simon, las estructuras proporcionan una semántica de "valor-tipo", por lo que si necesita un comportamiento similar al tipo de datos integrado, utilice una estructura. Como las estructuras pasan por copia, debes asegurarte de que sean de pequeño tamaño, alrededor de 16 bytes.

7

me gustaría utilizar estructuras cuando: se supone

  1. un objeto a ser de sólo lectura (cada vez que pasa/asignar una estructura que se copia). Los objetos de solo lectura son excelentes cuando se trata de procesamiento multiproceso ya que no requieren bloqueo en la mayoría de los casos.

  2. un objeto es pequeño y de vida corta. En tal caso, existe una buena posibilidad de que el objeto se asigne en la pila, lo que es mucho más eficiente que colocarlo en el montón administrado. Además, la memoria asignada por el objeto se liberará tan pronto como salga de su alcance. En otras palabras, es menos trabajo para Garbage Collector y la memoria se usa de manera más eficiente.

4

que siempre han utilizado una estructura cuando quería agrupar unos valores para pasar las cosas de nuevo a partir de una llamada a un método, pero no voy a necesitar usarlo para nada después de haber leído esos valores. Solo como una forma de mantener las cosas limpias. Tiendo a ver las cosas de una estructura de "usar y tirar" y cosas en una clase como la más útil y "funcional"

1

Hmm ...

Yo no usaría la recolección de basura como un argumento a favor/en contra de la uso de estructuras contra clases. El montón gestionado funciona de manera similar a una pila: la creación de un objeto simplemente lo coloca en la parte superior del montón, que es casi tan rápido como la asignación en la pila. Además, si un objeto es efímero y no sobrevive a un ciclo GC, la desasignación es gratuita, ya que el GC solo funciona con la memoria que todavía está accesible. (Busque en MSDN, hay una serie de artículos sobre administración de memoria .NET, soy demasiado flojo para buscarlos).

La mayoría de las veces utilizo una estructura, termino pateándome a mí mismo por hacerlo, porque más tarde descubro que tener semántica de referencia hubiera hecho las cosas un poco más simples.

De todos modos, esos cuatro puntos en el artículo de MSDN publicados anteriormente parecen ser una buena guía.

+0

Si a veces necesita semántica de referencia con una estructura, simplemente declare 'clase MutableHolder {public T Value; MutableHolder (valor T) {Value = valor;}} ', y luego un' MutableHolder 'será un objeto con semántica de clase mutable (esto funciona igual de bien si' T' es una estructura o un tipo de clase inmutable). – supercat

50

me sorprende que no he leído en cualquiera de la respuesta anterior esto, que considero el aspecto más crucial:

utilizo estructuras cuando quiero un tipo sin identidad. Por ejemplo un punto 3D:

public struct ThreeDimensionalPoint 
{ 
    public readonly int X, Y, Z; 
    public ThreeDimensionalPoint(int x, int y, int z) 
    { 
     this.X = x; 
     this.Y = y; 
     this.Z = z; 
    } 

    public override string ToString() 
    { 
     return "(X=" + this.X + ", Y=" + this.Y + ", Z=" + this.Z + ")"; 
    } 

    public override int GetHashCode() 
    { 
     return (this.X + 2)^(this.Y + 2)^(this.Z + 2); 
    } 

    public override bool Equals(object obj) 
    { 
     if (!(obj is ThreeDimensionalPoint)) 
      return false; 
     ThreeDimensionalPoint other = (ThreeDimensionalPoint)obj; 
     return this == other; 
    } 

    public static bool operator ==(ThreeDimensionalPoint p1, ThreeDimensionalPoint p2) 
    { 
     return p1.X == p2.X && p1.Y == p2.Y && p1.Z == p2.Z; 
    } 

    public static bool operator !=(ThreeDimensionalPoint p1, ThreeDimensionalPoint p2) 
    { 
     return !(p1 == p2); 
    } 
} 

Si tiene dos instancias de esta estructura que no les importa si son de una sola pieza de los datos en la memoria o dos. Solo te importan los valores que tienen.

+2

Bit fuera del tema, pero ¿por qué lanzas una ArgumentException cuando obj no es ThreeDimensionalPoint? ¿No deberías devolver falso en ese caso? – Svish

+2

Eso es correcto, estaba demasiado ansioso. 'return false' es lo que debería haber estado allí, corrigiendo ahora. –

25

Bill Wagner tiene un capítulo sobre esto en su libro "effective C#" (http://www.amazon.com/Effective-Specific-Ways-Improve-Your/dp/0321245660). Se concluye con el siguiente principio:

  1. es la principal responsabilidad del almacenamiento de datos de tipo?
  2. ¿Su interfaz pública está definida completamente por propiedades que acceden o modifican sus miembros de datos?
  3. ¿Estás seguro de que tu tipo nunca tendrá subclases?
  4. ¿Estás seguro de que tu tipo nunca será tratado polimórficamente?

Si responde "sí" a las 4 preguntas: utilice una estructura. De lo contrario, use una clase .

+1

entonces ... ¿los objetos de transferencia de datos (DTO) deberían ser estructuras? – cruizer

+1

Yo diría que sí, si cumple con los 4 criterios descritos anteriormente. ¿Por qué un objeto de transferencia de datos necesita ser tratado de una manera específica? –

+1

@cruizer depende de su situación. En un proyecto, teníamos campos de auditoría comunes en nuestros DTO y, por lo tanto, escribimos un DTO base del que otros heredaron. –

4

Si una entidad va a ser inmutable, la cuestión de si usar una estructura o una clase generalmente será de rendimiento en lugar de semántica. En un sistema de 32/64 bits, las referencias de clase requieren 4/8 bytes para almacenar, independientemente de la cantidad de información en la clase; copiar una referencia de clase requerirá copiar 4/8 bytes. Por otro lado, cada instancia de clase distinta de tendrá 8/16 bytes de sobrecarga además de la información que contiene y el costo de memoria de las referencias a ella.Supongamos que uno quiere una matriz de 500 entidades, cada una con cuatro enteros de 32 bits. Si la entidad es un tipo de estructura, la matriz requerirá 8,000 bytes independientemente de si las 500 entidades son todas idénticas, todas diferentes o en algún punto intermedio. Si la entidad es un tipo de clase, la matriz de 500 referencias tomará 4.000 bytes. Si todas esas referencias apuntan a objetos diferentes, los objetos requerirían 24 bytes adicionales cada uno (12,000 bytes para los 500), un total de 16,000 bytes, el doble del costo de almacenamiento de un tipo de estructura. Por otro lado, si el código creó una instancia de objeto y luego copió una referencia a las 500 ranuras de la matriz, el costo total sería de 24 bytes para esa instancia y de 4.000 para la matriz, un total de 4.024 bytes. Un gran ahorro. Pocas situaciones funcionarían tan bien como la última, pero en algunos casos podría ser posible copiar algunas referencias a suficientes ranuras de arreglos para que valga la pena compartirlas.

Si se supone que la entidad es mutable, la cuestión de si usar una clase o estructura es de alguna manera más fácil. Asumir "cosa" es o bien una estructura o clase que tiene un campo entero llamado x, y uno hace lo siguiente código:

 
    Thing t1,t2; 
    ... 
    t2 = t1; 
    t2.x = 5; 

¿Se quiere la última afirmación de afectar t1.x?

Si Thing es un tipo de clase, t1 y t2 serán equivalentes, lo que significa que t1.x y t2.x también serán equivalentes. Por lo tanto, la segunda declaración afectará a t1.x. Si Thing es un tipo de estructura, t1 y t2 serán instancias diferentes, lo que significa que t1.x y t2.x se referirán a diferentes enteros. Por lo tanto, la segunda declaración no afectará a t1.x.

Las estructuras mutables y las clases mutables tienen comportamientos fundamentalmente diferentes, aunque .net tiene algunas peculiaridades en el manejo de las mutaciones estructurales. Si uno quiere un comportamiento de tipo valor (lo que significa que "t2 = t1" copiará los datos de t1 a t2 dejando t1 y t2 como instancias distintas), y si uno puede vivir con las peculiaridades en el manejo de tipos de valores de .net, use una estructura. Si uno quiere semántica de tipo de valor pero los caprichos de .net causarían una semántica de tipo de valor roto en la aplicación, utilice una clase y murmure.

3

Además de las excelentes respuestas anteriores:

estructuras son tipos de valor.

Nunca se pueden establecer en Nada.

Estableciendo una estructura = Nada, establecerá todos sus tipos de valores a sus valores predeterminados.

1

Las estructuras están en la pila, no en el montón, por lo tanto, son seguras para roscas y deben utilizarse al implementar el patrón de objetos de transferencia, no desea usar objetos en el montón son volátiles, en este caso, debe usar la pila de llamadas, este es un caso básico para utilizar una estructura estoy sorprendido por todo el camino respuestas aquí,

5

utilizar una clase si:

  • Su identidad es importante. Las estructuras se copian implícitamente cuando se pasan por valor en un método.
  • Tendrá una gran huella de memoria.
  • Sus campos necesitan inicializadores.
  • Debe heredar de una clase base.
  • Necesita comportamiento polimórfico;

utilizar una estructura si:

  • Actuará como un tipo primitivo (int, long, byte, etc.).
  • Debe tener una huella de memoria pequeña.
  • Está llamando a un método P/Invoke que requiere que se pase una estructura por el valor .
  • Debe reducir el impacto de la recolección de basura en el rendimiento de la aplicación.
  • Sus campos deben inicializarse solo a sus valores predeterminados. Este valor sería cero para los tipos numéricos, falso para los tipos booleanos y nulo para los tipos de referencia.
    • Tenga en cuenta que en C# 6.0 las estructuras pueden tener un constructor predeterminado que se puede utilizar para inicializar los campos de la estructura en valores no predeterminados.
  • No necesita heredar de una clase base (que no sea ValueType, de la cual heredan todas las estructuras).
  • No necesita comportamiento polimórfico.
-1

Creo que la mejor respuesta es simplemente usar struct cuando lo que necesitas es una colección de propiedades, clase cuando se trata de un conjunto de propiedades Y comportamientos.

Cuestiones relacionadas