¿Cuándo deberías usar struct y no class en C #? Mi modelo conceptual es que las estructuras se usan en momentos en que el elemento es simplemente una colección de tipos de valor . Una forma de mantenerlos lógicamente todos juntos en un todo coherente.
Me encontré con estas reglas aquí :
- Una estructura debe representar un solo valor.
- Una estructura debe tener una huella de memoria inferior a 16 bytes.
- Una estructura no debe cambiarse después de la creación.
¿Estas reglas funcionan? ¿Qué significa una estructura semánticamente?
System.Drawing.Rectangle
viola las tres reglas.Respuestas:
La fuente a la que hace referencia el OP tiene cierta credibilidad ... pero ¿qué pasa con Microsoft? ¿Cuál es la postura sobre el uso de la estructura? Busqué algo de aprendizaje adicional de Microsoft , y esto es lo que encontré:
Microsoft viola constantemente esas reglas
Bien, # 2 y # 3 de todos modos. Nuestro querido diccionario tiene 2 estructuras internas:
* Fuente de referencia
La fuente 'JonnyCantCode.com' obtuvo 3 de 4, bastante perdonable ya que el # 4 probablemente no sería un problema. Si se encuentra boxeando una estructura, reconsidere su arquitectura.
Veamos por qué Microsoft usaría estas estructuras:
Entry
yEnumerator
representa valores únicos.Entry
nunca se pasa como parámetro fuera de la clase Diccionario. La investigación adicional muestra que para satisfacer la implementación de IEnumerable, Dictionary utiliza laEnumerator
estructura que copia cada vez que se solicita un enumerador ... tiene sentido.Enumerator
es público porque Dictionary es enumerable y debe tener igual accesibilidad a la implementación de la interfaz IEnumerator, por ejemplo, IEnumerator getter.Actualización : además, tenga en cuenta que cuando una estructura implementa una interfaz, como lo hace Enumerator, y se convierte a ese tipo implementado, la estructura se convierte en un tipo de referencia y se mueve al montón. Interna a la clase Diccionario, Enumerator sigue siendo un tipo de valor. Sin embargo, tan pronto como un método llama
GetEnumerator()
,IEnumerator
se devuelve un tipo de referencia .Lo que no vemos aquí es ningún intento o prueba de requisito para mantener las estructuras inmutables o mantener un tamaño de instancia de solo 16 bytes o menos:
readonly
, no es inmutableEntry
tiene una vida indeterminado (deAdd()
, aRemove()
,Clear()
o la recolección de basura);Y ... 4. Ambas estructuras almacenan TKey y TValue, que todos sabemos que son bastante capaces de ser tipos de referencia (información adicional)
A pesar de las claves hash, los diccionarios son rápidos en parte porque instaurar una estructura es más rápido que un tipo de referencia. Aquí, tengo un
Dictionary<int, int>
que almacena 300,000 enteros aleatorios con claves incrementadas secuencialmente.Capacidad : número de elementos disponibles antes de que se deba cambiar el tamaño de la matriz interna.
MemSize : determinado serializando el diccionario en un MemoryStream y obteniendo una longitud de byte (lo suficientemente precisa para nuestros propósitos).
Redimensionado completado : el tiempo que lleva cambiar el tamaño de la matriz interna de 150862 elementos a 312874 elementos. Cuando te das cuenta de que cada elemento se copia secuencialmente
Array.CopyTo()
, eso no es nada malo.Tiempo total para llenar : admitidamente sesgado debido al registro y un
OnResize
evento que agregué a la fuente; Sin embargo, sigue siendo impresionante llenar 300k enteros mientras se redimensiona 15 veces durante la operación. Solo por curiosidad, ¿cuál sería el tiempo total para llenar si ya conociera la capacidad? 13msEntonces, ¿y si
Entry
fuera una clase? ¿Estos tiempos o métricas realmente diferirían tanto?Obviamente, la gran diferencia está en cambiar el tamaño. ¿Hay alguna diferencia si el Diccionario se inicializa con la Capacidad? No es suficiente para preocuparse por ... 12ms .
Lo que sucede es que, como
Entry
es una estructura, no requiere inicialización como un tipo de referencia. Esto es tanto la belleza como la ruina del tipo de valor. Para usarEntry
como tipo de referencia, tuve que insertar el siguiente código:La razón por la que tuve que inicializar cada elemento de matriz
Entry
como tipo de referencia se puede encontrar en MSDN: Diseño de estructura . En breve:En realidad es bastante simple y tomaremos prestado de las Tres leyes de la robótica de Asimov :
... qué nos quitamos de esto : en resumen, ser responsables con el uso de los tipos de valor. Son rápidos y eficientes, pero tienen la capacidad de causar muchos comportamientos inesperados si no se mantienen adecuadamente (es decir, copias no intencionales).
fuente
Decimal
oDateTime
], entonces si no cumpliera con las otras tres reglas, debería ser reemplazada por una clase. Si una estructura contiene una colección fija de variables, cada una de las cuales puede contener cualquier valor que sea válido para su tipo [pRectangle
. Ej. ], Entonces debe cumplir con diferentes reglas, algunas de las cuales son contrarias a las de las estructuras de "valor único" .Dictionary
tipo de entrada sobre la base de que es solo un tipo interno, el rendimiento se consideró más importante que la semántica, o alguna otra excusa. Mi punto es que un tipo comoRectangle
debería tener su contenido expuesto como campos editables individualmente no "porque" los beneficios de rendimiento superan las imperfecciones semánticas resultantes, sino porque el tipo representa semánticamente un conjunto fijo de valores independientes , por lo que la estructura mutable es a la vez más performante y semánticamente superior .Cuando usted:
Sin embargo, la advertencia es que las estructuras (arbitrariamente grandes) son más caras de repartir que las referencias de clase (generalmente una palabra de máquina), por lo que las clases podrían terminar siendo más rápidas en la práctica.
fuente
(Guid)null
(está bien emitir un valor nulo a un tipo de referencia), entre otras cosas.No estoy de acuerdo con las reglas dadas en la publicación original. Aquí están mis reglas:
1) Utiliza estructuras para el rendimiento cuando se almacena en matrices. (ver también ¿ Cuándo son las estructuras la respuesta? )
2) Los necesita en el código que pasa datos estructurados a / desde C / C ++
3) No use estructuras a menos que las necesite:
fuente
struct
saber cómo se comportará, pero si algo tienestruct
campos expuestos, eso es todo lo que uno debe saber. Si un objeto expone una propiedad de un tipo de estructura de campo expuesto, y si el código lee esa estructura a una variable y la modifica, se puede predecir con seguridad que dicha acción no afectará al objeto cuya propiedad se leyó a menos que se escriba la estructura espalda. Por el contrario, si la propiedad fuera un tipo de clase mutable, leerlo y modificarlo podría actualizar el objeto subyacente como se esperaba, pero ...Utilice una estructura cuando desee semántica de valor en lugar de semántica de referencia.
Editar
No estoy seguro de por qué la gente está rechazando esto, pero este es un punto válido, y se hizo antes de que el operador aclarara su pregunta, y es la razón básica más fundamental para una estructura.
Si necesita semántica de referencia, necesita una clase, no una estructura.
fuente
Además de la respuesta "es un valor", un escenario específico para usar estructuras es cuando sabes que tienes un conjunto de datos que está causando problemas de recolección de basura, y tienes muchos objetos. Por ejemplo, una gran lista / matriz de instancias de Persona. La metáfora natural aquí es una clase, pero si tiene una gran cantidad de instancias de Persona de larga duración, pueden terminar obstruyendo GEN-2 y causar paradas de GC. Si el escenario lo justifica, un enfoque potencial aquí es usar una matriz (no una lista) de estructuras de Persona , es decir
Person[]
. Ahora, en lugar de tener millones de objetos en GEN-2, tiene un solo fragmento en el LOH (supongo que no hay cadenas, etc., es decir, un valor puro sin referencias). Esto tiene muy poco impacto de GC.Trabajar con estos datos es incómodo, ya que los datos probablemente estén sobredimensionados para una estructura, y no desea copiar valores gordos todo el tiempo. Sin embargo, acceder a él directamente en una matriz no copia la estructura, está en su lugar (en contraste con un indexador de lista, que copia). Esto significa mucho trabajo con índices:
Tenga en cuenta que mantener los valores en sí mismos inmutables ayudará aquí. Para una lógica más compleja, use un método con un parámetro by-ref:
Una vez más, esto está en su lugar: no hemos copiado el valor.
En escenarios muy específicos, esta táctica puede ser muy exitosa; sin embargo, es un scernario bastante avanzado que debe intentarse solo si sabe lo que está haciendo y por qué. El valor predeterminado aquí sería una clase.
fuente
List
Creo, utiliza unArray
detrás de escena. No ?De la especificación del lenguaje C # :
Una alternativa es hacer de Point una estructura.
Ahora, solo se crea una instancia de un objeto, el de la matriz, y las instancias de Point se almacenan en línea en la matriz.
Los constructores de estructura se invocan con el nuevo operador, pero eso no implica que se esté asignando memoria. En lugar de asignar dinámicamente un objeto y devolverle una referencia, un constructor de estructura simplemente devuelve el valor de estructura en sí (generalmente en una ubicación temporal en la pila), y este valor se copia según sea necesario.
Con las clases, es posible que dos variables hagan referencia al mismo objeto y, por lo tanto, que las operaciones en una variable afecten al objeto al que hace referencia la otra variable. Con las estructuras, las variables tienen cada una su propia copia de los datos, y no es posible que las operaciones en una afecten a la otra. Por ejemplo, la salida producida por el siguiente fragmento de código depende de si Point es una clase o una estructura.
Si Point es una clase, la salida es 20 porque a y b hacen referencia al mismo objeto. Si Point es una estructura, el resultado es 10 porque la asignación de a a b crea una copia del valor, y esta copia no se ve afectada por la asignación posterior a ax
El ejemplo anterior destaca dos de las limitaciones de las estructuras. Primero, copiar una estructura completa suele ser menos eficiente que copiar una referencia de objeto, por lo que la transferencia de parámetros de asignación y valor puede ser más costosa con estructuras que con tipos de referencia. En segundo lugar, a excepción de los parámetros ref y out, no es posible crear referencias a estructuras, lo que descarta su uso en varias situaciones.
fuente
ref
a una estructura mutable y saber que cualquier mutación que realice el método externo se realizará antes de que regrese. Es una lástima .net no tiene ningún concepto de parámetros efímeros y valores de retorno de funciones, ya que ...ref
lograr la semántica ventajosa de estructuras pasadas con objetos de clase. Esencialmente, las variables locales, los parámetros y los valores de retorno de la función pueden ser persistentes (por defecto), retornables o efímeros. Se prohibiría que Code copiara cosas efímeras a cualquier cosa que sobreviviera al alcance actual. Las cosas retornables serían como cosas efímeras, excepto que podrían ser devueltas de una función. El valor de retorno de una función estaría sujeto a las restricciones más estrictas aplicables a cualquiera de sus parámetros "retornables".Las estructuras son buenas para la representación atómica de datos, donde dichos datos pueden copiarse varias veces por el código. Clonar un objeto es en general más costoso que copiar una estructura, ya que implica asignar la memoria, ejecutar el constructor y desasignar / recolectar basura cuando se hace con él.
fuente
Aquí hay una regla básica.
Si todos los campos miembros son tipos de valor, cree una estructura .
Si algún campo miembro es un tipo de referencia, cree una clase . Esto se debe a que el campo de tipo de referencia necesitará la asignación del montón de todos modos.
Exmaples
fuente
string
son semánticamente equivalentes a los valores, y almacenar una referencia a un objeto inmutable en un campo no implica una asignación de montón. La diferencia entre una estructura con campos públicos expuestos y un objeto de clase con campos públicos expuestos es que, dada la secuencia de códigovar q=p; p.X=4; q.X=5;
,p.X
tendrá el valor 4 sia
es un tipo de estructura y 5 si es un tipo de clase. Si desea poder modificar convenientemente los miembros del tipo, debe seleccionar 'clase' o 'estructura' en función de si desea que los cambiosq
afectenp
.ArraySegment<T>
encapsula aT[]
, que siempre es un tipo de clase. El tipo de estructura aKeyValuePair<TKey,TValue>
menudo se usa con tipos de clase como parámetros genéricos.Primero: escenarios de interoperabilidad o cuando necesita especificar el diseño de la memoria
Segundo: cuando los datos son casi del mismo tamaño que un puntero de referencia de todos modos.
fuente
Debe usar una "estructura" en situaciones en las que desea especificar explícitamente el diseño de memoria utilizando StructLayoutAttribute , generalmente para PInvoke.
Editar: El comentario señala que puede usar class o struct con StructLayoutAttribute y eso es cierto. En la práctica, normalmente usaría una estructura: se asigna en la pila frente al montón, lo que tiene sentido si solo está pasando un argumento a una llamada a un método no administrado.
fuente
Utilizo estructuras para empacar o desempacar cualquier tipo de formato de comunicación binario. Eso incluye leer o escribir en el disco, listas de vértices DirectX, protocolos de red o tratar datos cifrados / comprimidos.
Las tres pautas que enumeras no me han sido útiles en este contexto. Cuando necesito escribir cuatrocientos bytes de cosas en un orden particular, voy a definir una estructura de cuatrocientos bytes, y la llenaré con los valores no relacionados que se supone que tiene, y voy a configurarlo de la manera que tenga más sentido en ese momento. (De acuerdo, cuatrocientos bytes serían bastante extraños, pero cuando estaba escribiendo archivos de Excel para vivir, estaba lidiando con estructuras de hasta unos cuarenta bytes, porque así de grandes son algunos de los registros BIFF).
fuente
Con la excepción de los valuetypes que son utilizados directamente por el tiempo de ejecución y varios otros para propósitos de PInvoke, solo debe usar valuetypes en 2 escenarios.
fuente
this
parámetro efímero utilizado para invocar sus métodos); Las clases permiten duplicar referencias..NET admite
value types
yreference types
(en Java, puede definir solo tipos de referencia). Las instanciasreference types
se asignan en el montón administrado y se recolectan cuando no hay referencias pendientes a ellas. Las instancias devalue types
, por otro lado, se asignan en elstack
, y por lo tanto, la memoria asignada se reclama tan pronto como finaliza su alcance. Y, por supuesto,value types
pasar por valor yreference types
por referencia. Todos los tipos de datos primitivos de C #, excepto System.String, son tipos de valores.Cuando usar struct sobre clase,
En C #,
structs
arevalue types
, las clases sonreference types
. Puede crear tipos de valor, en C #, utilizando laenum
palabra clave y lastruct
palabra clave. El uso de a envalue type
lugar de areference type
dará como resultado menos objetos en el montón administrado, lo que resulta en una menor carga en el recolector de basura (GC), ciclos de GC menos frecuentes y, en consecuencia, un mejor rendimiento. Sin embargo, tambiénvalue types
tienen sus desventajas. Pasar por altostruct
es definitivamente más costoso que pasar una referencia, ese es un problema obvio. El otro problema es la sobrecarga asociada aboxing/unboxing
. En caso de que se pregunte québoxing/unboxing
significa, siga estos enlaces para obtener una buena explicación sobreboxing
yunboxing
. Además del rendimiento, hay momentos en los que simplemente necesita tipos para tener una semántica de valor, que sería muy difícil (o feo) implementar sireference types
es todo lo que tiene. Debe usarvalue types
solamente, cuando necesite semántica de copia o necesite inicialización automática, normalmente enarrays
estos tipos.fuente
ref
. Pasar cualquier estructura de tamaño porref
costos es lo mismo que pasar una referencia de clase por valor. Copiar cualquier estructura de tamaño o pasar por valor es más barato que realizar una copia defensiva de un objeto de clase y almacenar o pasar una referencia a eso. Las clases de Big Times son mejores que las estructuras para almacenar valores son (1) cuando las clases son inmutables (para evitar copias defensivas), y cada instancia que se crea se pasará mucho, o ...readOnlyStruct.someMember = 5;
no es hacersomeMember
una propiedad de solo lectura, sino convertirla en un campo.Una estructura es un tipo de valor. Si asigna una estructura a una nueva variable, la nueva variable contendrá una copia del original.
La ejecución de los siguientes resultados en 5 instancias de la estructura almacenada en la memoria:
Una clase es un tipo de referencia. Cuando asigna una clase a una nueva variable, la variable contiene una referencia al objeto de clase original.
La ejecución de los siguientes resultados en una sola instancia del objeto de clase en la memoria.
Las estructuras pueden aumentar la probabilidad de un error de código. Si un objeto de valor se trata como un objeto de referencia mutable, un desarrollador puede sorprenderse cuando los cambios realizados se pierden inesperadamente.
fuente
Hice un pequeño punto de referencia con BenchmarkDotNet para comprender mejor el beneficio de "estructura" en números. Estoy probando el bucle a través de la matriz (o lista) de estructuras (o clases). La creación de esas matrices o listas está fuera del alcance del punto de referencia: está claro que la "clase" es más pesada utilizará más memoria e involucrará GC.
Entonces, la conclusión es: tenga cuidado con LINQ y las estructuras ocultas que encajonan / desempaquetan y que usan estructuras para microoptimizaciones permanecen estrictamente con las matrices.
PD Otro punto de referencia sobre el paso de struct / class a través de la pila de llamadas es https://stackoverflow.com/a/47864451/506147
Código:
fuente
Los tipos de estructura en C # u otros lenguajes .net se usan generalmente para contener cosas que deberían comportarse como grupos de valores de tamaño fijo. Un aspecto útil de los tipos de estructura es que los campos de una instancia de tipo de estructura pueden modificarse modificando la ubicación de almacenamiento en la que se encuentra, y de ninguna otra manera. Es posible codificar una estructura de tal manera que la única forma de mutar cualquier campo sea construir una instancia completamente nueva y luego usar una asignación de estructura para mutar todos los campos del objetivo sobrescribiéndolos con valores de la nueva instancia, pero a menos que una estructura no proporcione medios para crear una instancia en la que sus campos tengan valores no predeterminados, todos sus campos serán mutables si la estructura misma se almacena en una ubicación mutable.
Tenga en cuenta que es posible diseñar un tipo de estructura para que esencialmente se comporte como un tipo de clase, si la estructura contiene un campo de tipo de clase privado y redirige a sus propios miembros al del objeto de clase envuelto. Por ejemplo, un
PersonCollection
podría ofrecer propiedadesSortedByName
ySortedById
, ambos tienen una referencia "inmutable" a unPersonCollection
(establecido en su constructor) y se implementanGetEnumerator
llamando acreator.GetNameSortedEnumerator
ocreator.GetIdSortedEnumerator
. Tales estructuras se comportarían de manera muy similar a una referencia a aPersonCollection
, excepto que susGetEnumerator
métodos estarían vinculados a diferentes métodos enPersonCollection
. También se podría tener una estructura que envuelva una parte de una matriz (por ejemplo, se podría definir unaArrayRange<T>
estructura que contendría unT[]
llamadoArr
, un intOffset
y un intLength
, con una propiedad indexada a la que, para un índiceidx
en el rango de 0 aLength-1
, accederíaArr[idx+Offset]
). Desafortunadamente, si sefoo
trata de una instancia de solo lectura de dicha estructura, las versiones actuales del compilador no permitirán operaciones comofoo[3]+=4;
porque no tienen forma de determinar si tales operaciones intentarían escribir en campos defoo
.También es posible diseñar una estructura que se comporte como un tipo de valor que contenga una colección de tamaño variable (que parecerá copiarse siempre que la estructura sea), pero la única forma de hacer que funcione es asegurarse de que no haya ningún objeto struct tiene una referencia que siempre estará expuesta a cualquier cosa que pueda mutarla. Por ejemplo, uno podría tener una estructura similar a una matriz que contiene una matriz privada, y cuyo método de "colocación" indexado crea una nueva matriz cuyo contenido es similar al del original, excepto por un elemento modificado. Desafortunadamente, puede ser algo difícil hacer que tales estructuras funcionen eficientemente. Si bien hay momentos en que la semántica de estructura puede ser conveniente (por ejemplo, poder pasar una colección tipo matriz a una rutina, tanto el llamador como el que llama saben que el código externo no modificará la colección,
fuente
No, no estoy totalmente de acuerdo con las reglas. Son buenas pautas a tener en cuenta con el rendimiento y la estandarización, pero no a la luz de las posibilidades.
Como puede ver en las respuestas, hay muchas formas creativas de usarlas. Por lo tanto, estas pautas deben ser solo eso, siempre en aras del rendimiento y la eficiencia.
En este caso, uso clases para representar objetos del mundo real en su forma más grande, uso estructuras para representar objetos más pequeños que tienen usos más exactos. La forma en que lo dijiste, "un todo más coherente". La palabra clave es coherente. Las clases serán más elementos orientados a objetos, mientras que las estructuras pueden tener algunas de esas características, aunque en menor escala. OMI
Los uso mucho en las etiquetas Treeview y Listview donde se puede acceder a los atributos estáticos comunes muy rápidamente. Siempre me ha costado obtener esta información de otra manera. Por ejemplo, en mis aplicaciones de base de datos, uso una vista de árbol donde tengo tablas, SP, funciones o cualquier otro objeto. Creo y relleno mi estructura, la pongo en la etiqueta, la extraigo, obtengo los datos de la selección, etc. ¡No haría esto con una clase!
Intento mantenerlos pequeños, usarlos en situaciones de instancia única y evitar que cambien. Es prudente tener en cuenta la memoria, la asignación y el rendimiento. Y las pruebas son muy necesarias.
fuente
double
valores para esas coordenadas, tal especificación lo obligaría a comportarse semánticamente de manera idéntica a una estructura de campo expuesto, excepto por algunos detalles del comportamiento de subprocesos múltiples (la clase inmutable sería mejor en algunos casos, mientras que la estructura de campo expuesto sería mejor en otros; una estructura llamada "inmutable" sería ser peor en todos los casos).Mi regla es
1, siempre use clase;
2, si hay algún problema de rendimiento, trato de cambiar alguna clase para estructurar según las reglas que mencionó @IAbstract, y luego hago una prueba para ver si estos cambios pueden mejorar el rendimiento.
fuente
Foo
encapsule una colección fija de valores independientes (p. Ej., Coordenadas de un punto) que a veces querrá pasar como grupo y a veces quiere cambiar de forma independiente. No he encontrado ningún patrón para usar clases que combine ambos propósitos casi tan bien como una estructura simple de campo expuesto (que, al ser una colección fija de variables independientes, se ajusta perfectamente).public readonly
campos en mis tipos también, porque crear propiedades de solo lectura es simplemente demasiado trabajo para prácticamente ningún beneficio.)MyListOfPoint[3].Offset(2,3);
envar temp=MyListOfPoint[3]; temp.Offset(2,3);
, una transformación que es falsa cuando se aplica ...Offset
método. La forma correcta de evitar ese código falso no debe ser hacer que las estructuras sean inmutables innecesariamente, sino permitir que métodos comoOffset
ser etiquetados con un atributo que prohíba la transformación mencionada anteriormente. Las conversiones numéricas implícitas también podrían haber sido mucho mejores si se pudieran etiquetar para que sean aplicables solo en los casos en que su invocación sería obvia. Si existen sobrecargas parafoo(float,float)
yfoo(double,double)
, me postulo que tratar de utilizar unafloat
y unadouble
frecuencia no se debería aplicar una conversión implícita, pero en su lugar debería haber un error.double
valor a afloat
, o pasarlo a un método que puede tomar unfloat
argumento pero nodouble
, casi siempre haría lo que el programador pretendía. Por el contrario, asignarfloat
expresiones adouble
sin un tipo de letra explícito es a menudo un error. El único momento en que permitir ladouble->float
conversión implícita causaría problemas sería cuando se seleccionara una sobrecarga menos que ideal. Yo diría que la forma correcta de prevenir eso no debería haber sido prohibir implícitamente double-> float, sino etiquetar las sobrecargas con atributos para no permitir la conversión.Una clase es un tipo de referencia. Cuando se crea un objeto de la clase, la variable a la que se asigna el objeto contiene solo una referencia a esa memoria. Cuando la referencia del objeto se asigna a una nueva variable, la nueva variable se refiere al objeto original. Los cambios realizados a través de una variable se reflejan en la otra variable porque ambos se refieren a los mismos datos. Una estructura es un tipo de valor. Cuando se crea una estructura, la variable a la que se asigna la estructura contiene los datos reales de la estructura. Cuando la estructura se asigna a una nueva variable, se copia. La nueva variable y la variable original, por lo tanto, contienen dos copias separadas de los mismos datos. Los cambios realizados en una copia no afectan a la otra copia. En general, las clases se utilizan para modelar comportamientos más complejos o datos que se pretende modificar después de crear un objeto de clase.
Clases y estructuras (Guía de programación de C #)
fuente
MITO # 1: LAS ESTRUCTAS SON CLASES LIGERAS
Este mito viene en una variedad de formas. Algunas personas creen que los tipos de valor no pueden o no deben tener métodos u otro comportamiento significativo; deben usarse como tipos simples de transferencia de datos, con solo campos públicos o propiedades simples. El tipo DateTime es un buen contraejemplo para esto: tiene sentido que sea un tipo de valor, en términos de ser una unidad fundamental como un número o un carácter, y también tiene sentido que pueda realizar cálculos basados en es valioso. Mirando las cosas desde la otra dirección, los tipos de transferencia de datos a menudo deberían ser tipos de referencia de todos modos; la decisión debería basarse en el valor deseado o la semántica del tipo de referencia, no en la simplicidad del tipo. Otras personas creen que los tipos de valor son "más ligeros" que los tipos de referencia en términos de rendimiento. La verdad es que, en algunos casos, los tipos de valor son más efectivos: no requieren recolección de basura a menos que estén encuadrados, no tienen la sobrecarga de identificación de tipo y no requieren desreferenciación, por ejemplo. Pero de otras maneras, los tipos de referencia son más eficaces: el paso de parámetros, la asignación de valores a variables, la devolución de valores y operaciones similares solo requieren la copia de 4 u 8 bytes (dependiendo de si está ejecutando el CLR de 32 o 64 bits) ) en lugar de copiar todos los datos. ¡Imagínese si ArrayList fuera de alguna manera un tipo de valor "puro", y pasar una expresión ArrayList a un método implicara copiar todos sus datos! En casi todos los casos, el rendimiento no está realmente determinado por este tipo de decisión de todos modos. Los cuellos de botella casi nunca están donde cree que estarán, y antes de tomar una decisión de diseño basada en el rendimiento, Debes medir las diferentes opciones. Vale la pena señalar que la combinación de las dos creencias tampoco funciona. No importa cuántos métodos tenga un tipo (ya sea una clase o una estructura), la memoria tomada por instancia no se ve afectada. (Hay un costo en términos de la memoria ocupada para el código en sí, pero se incurre una vez en lugar de para cada instancia).
MITO # 2: TIPOS DE REFERENCIA EN VIVO EN EL HEAP; TIPOS DE VALOR EN VIVO EN LA PILA
Esto a menudo es causado por la pereza de la persona que lo repite. La primera parte es correcta: siempre se crea una instancia de un tipo de referencia en el montón. Es la segunda parte la que causa problemas. Como ya he señalado, el valor de una variable vive donde se declara, por lo que si tiene una clase con una variable de instancia de tipo int, el valor de esa variable para cualquier objeto dado siempre estará donde está el resto de los datos para el objeto: en el montón Solo las variables locales (variables declaradas dentro de los métodos) y los parámetros del método viven en la pila. En C # 2 y posteriores, incluso algunas variables locales no viven realmente en la pila, como verá cuando veamos métodos anónimos en el capítulo 5. ¿ESTOS ESTOS CONCEPTOS SON PERTINENTES AHORA? Es discutible que si está escribiendo código administrado, debe dejar que el tiempo de ejecución se preocupe por la mejor forma de usar la memoria. En efecto, la especificación del lenguaje no garantiza qué vive dónde; un futuro tiempo de ejecución puede crear algunos objetos en la pila si sabe que puede salirse con la suya, o el compilador de C # podría generar código que casi no usa la pila. El siguiente mito suele ser solo un problema de terminología.
MITO # 3: LOS OBJETOS SE PASAN POR REFERENCIA EN C # POR DEFECTO
Este es probablemente el mito más ampliamente propagado. Una vez más, las personas que hacen esta afirmación a menudo (aunque no siempre) saben cómo se comporta realmente C #, pero no saben qué significa realmente "pasar por referencia". Desafortunadamente, esto es confuso para las personas que saben lo que significa. La definición formal de pasar por referencia es relativamente complicada, involucra valores de l y terminología similar de informática, pero lo importante es que si pasa una variable por referencia, el método que está llamando puede cambiar el valor de la variable de la persona que llama cambiando su valor de parámetro. Ahora, recuerde que el valor de una variable de tipo de referencia es la referencia, no el objeto en sí. Puede cambiar el contenido del objeto al que se refiere un parámetro sin que el parámetro mismo se pase por referencia. Por ejemplo,
Cuando se llama a este método, el valor del parámetro (una referencia a un StringBuilder) se pasa por valor. Si tuviera que cambiar el valor de la variable del generador dentro del método, por ejemplo, con el enunciado generador = nulo; ese cambio no sería visto por la persona que llama, contrario al mito. Es interesante notar que no solo el bit "por referencia" del mito es inexacto, sino también el bit "los objetos se pasan". Los objetos mismos nunca se pasan, ya sea por referencia o por valor. Cuando se trata de un tipo de referencia, la variable se pasa por referencia o el valor del argumento (la referencia) se pasa por valor. Aparte de cualquier otra cosa, esto responde a la pregunta de qué sucede cuando nulo se usa como argumento de valor: si los objetos se pasaran, eso causaría problemas, ¡ya que no habría un objeto para pasar! En lugar, la referencia nula se pasa por valor de la misma manera que cualquier otra referencia sería. Si esta explicación rápida lo ha dejado desconcertado, puede consultar mi artículo, "Parámetros que pasan en C #" (http://mng.bz/otVt ), que entra en mucho más detalle. Estos mitos no son los únicos que existen. El boxeo y el desempaquetado vienen por su parte de malentendidos, que trataré de aclarar a continuación.
Referencia: C # in Depth 3rd Edition por Jon Skeet
fuente
Creo que una buena primera aproximación es "nunca".
Creo que una buena segunda aproximación es "nunca".
Si está desesperado por perf, considérelos, pero siempre mida.
fuente
Estaba tratando con la Canalización con nombre de Windows Communication Foundation [WCF] y noté que tiene sentido usar Structs para garantizar que el intercambio de datos sea de tipo de valor en lugar de tipo de referencia .
fuente
La estructura C # es una alternativa ligera a una clase. Puede hacer casi lo mismo que una clase, pero es menos "costoso" usar una estructura en lugar de una clase. La razón de esto es un poco técnica, pero en resumen, las nuevas instancias de una clase se colocan en el montón, donde las estructuras recién instanciadas se colocan en la pila. Además, no se trata de referencias a estructuras, como las clases, sino que se trabaja directamente con la instancia de estructura. Esto también significa que cuando pasa una estructura a una función, es por valor, en lugar de como referencia. Hay más sobre esto en el capítulo sobre parámetros de función.
Por lo tanto, debe usar estructuras cuando desee representar estructuras de datos más simples, y especialmente si sabe que creará una gran cantidad de ellas. Hay muchos ejemplos en el marco .NET, donde Microsoft ha usado estructuras en lugar de clases, por ejemplo, la estructura Punto, Rectángulo y Color.
fuente
Struct se puede utilizar para mejorar el rendimiento de la recolección de basura. Si bien generalmente no tiene que preocuparse por el rendimiento del GC, hay escenarios en los que puede ser mortal. Como grandes cachés en aplicaciones de baja latencia. Ver esta publicación para un ejemplo:
http://00sharp.wordpress.com/2013/07/03/a-case-for-the-struct/
fuente
Los tipos de estructura o valor se pueden usar en los siguientes escenarios:
Puede obtener más información sobre los tipos de valores y los tipos de valores aquí en este enlace
fuente
Brevemente, use struct si:
1- sus propiedades / campos de objeto no necesitan ser cambiados. Quiero decir que solo quieres darles un valor inicial y luego leerlos.
2- las propiedades y los campos en su objeto son de tipo de valor y no son tan grandes.
Si ese es el caso, puede aprovechar las estructuras para un mejor rendimiento y una asignación de memoria optimizada, ya que solo usan pilas en lugar de pilas y montones (en clases)
fuente
Raramente uso una estructura para las cosas. Pero solo soy yo. Depende de si necesito que el objeto sea anulable o no.
Como se indicó en otras respuestas, uso clases para objetos del mundo real. También tengo la mentalidad de que las estructuras se utilizan para almacenar pequeñas cantidades de datos.
fuente
Las estructuras son, en la mayoría de los casos, como clases / objetos. La estructura puede contener funciones, miembros y puede ser heredada. Pero las estructuras en C # se usan solo para la retención de datos . Las estructuras requieren menos RAM que las clases y son más fáciles de recolectar para el recolector de basura . Pero cuando usa funciones en su estructura, el compilador en realidad toma esa estructura de manera muy similar a la clase / objeto, por lo que si desea algo con funciones, use clase / objeto .
fuente