Una cadena es un tipo de referencia, aunque tiene la mayoría de las características de un tipo de valor, como ser inmutable y tener == sobrecargado para comparar el texto en lugar de asegurarse de que hacen referencia al mismo objeto.
¿Por qué la cadena no es solo un tipo de valor entonces?
c#
string
clr
value-type
reference-type
Davy8
fuente
fuente
is
lado las pruebas), la respuesta es probablemente "por razones históricas". El rendimiento de la copia no puede ser la razón, ya que no es necesario copiar físicamente objetos inmutables. Ahora es imposible cambiar sin romper el código que realmente usais
cheques (o restricciones similares).std::string
comportarse como una colección es un antiguo error que no se puede solucionar ahora.Respuestas:
Las cadenas no son tipos de valor, ya que pueden ser enormes y deben almacenarse en el montón. Los tipos de valor se almacenan en la pila (en todas las implementaciones de CLR hasta el momento). La asignación de cadenas de la pila rompería todo tipo de cosas: la pila es de solo 1 MB para 32 bits y 4 MB para 64 bits, tendría que encuadrar cada cadena, incurriendo en una penalización de copia, no podría internar cadenas y uso de memoria globo, etc ...
(Editar: se agregó una aclaración sobre el almacenamiento de tipos de valor como un detalle de implementación, lo que lleva a esta situación en la que tenemos un tipo con valores semánticos que no heredan de System.ValueType. Gracias Ben).
fuente
String
no es de tamaño variable. Cuando le agrega, en realidad está creando otroString
objeto, asignándole nueva memoria.Int32
siempre es de 4 bytes, por lo tanto, el compilador asigna 4 bytes cada vez que define una variable de cadena. ¿Cuánta memoria debe asignar el compilador cuando encuentra unaint
variable (si fuera un tipo de valor)? Comprenda que el valor aún no se ha asignado en ese momento.Int32
siempre es de 4 bytes, por lo tanto, el compilador asigna 4 bytes cada vez que define unaint
variable. ¿Cuánta memoria debe asignar el compilador cuando encuentra unastring
variable (si fuera un tipo de valor)? Comprenda que el valor aún no se ha asignado en ese momento.No es un tipo de valor porque el rendimiento (¡espacio y tiempo!) Sería terrible si fuera un tipo de valor y su valor tuviera que copiarse cada vez que se pasa y se devuelve desde métodos, etc.
Tiene una semántica de valor para mantener al mundo cuerdo. ¿Te imaginas lo difícil que sería codificar si
listo
b
para serfalse
? Imagine lo difícil que sería codificar casi cualquier aplicación.fuente
new String("foo");
y otrasnew String("foo")
pueden evaluar en la misma referencia, qué tipo de no es lo que esperaría que hiciera unnew
operador. (¿O puede decirme un caso en el que me gustaría comparar las referencias?)ReferenceEquals(x, y)
es una prueba rápida y puede devolver 0 inmediatamente, y cuando se mezcla con su prueba nula ni siquiera agrega más trabajo.string
podría comportarse como una cadena vacía (como lo era en los sistemas pre.net) en lugar de como una referencia nula. En realidad, mi preferencia sería tener un tipo de valorString
que contuviera un tipo de referenciaNullableString
, con el primero con un valor predeterminado equivalenteString.Empty
y el segundo con un valor predeterminadonull
, y con reglas especiales de boxing / unboxing (como boxing un default- valoradoNullableString
daría una referencia aString.Empty
).La distinción entre los tipos de referencia y los tipos de valor son básicamente una compensación de rendimiento en el diseño del lenguaje. Los tipos de referencia tienen algunos gastos generales en la construcción y la destrucción y la recolección de basura, ya que se crean en el montón. Por otro lado, los tipos de valor tienen una sobrecarga en las llamadas a métodos (si el tamaño de los datos es mayor que un puntero), porque todo el objeto se copia en lugar de solo un puntero. Debido a que las cadenas pueden ser (y típicamente son) mucho más grandes que el tamaño de un puntero, están diseñadas como tipos de referencia. Además, como señaló Servy, el tamaño de un tipo de valor debe conocerse en el momento de la compilación, que no siempre es el caso de las cadenas.
La cuestión de la mutabilidad es un tema aparte. Tanto los tipos de referencia como los tipos de valor pueden ser mutables o inmutables. Sin embargo, los tipos de valor suelen ser inmutables, ya que la semántica para los tipos de valor mutable puede ser confusa.
Los tipos de referencia son generalmente mutables, pero pueden diseñarse como inmutables si tiene sentido. Las cadenas se definen como inmutables porque hacen posibles ciertas optimizaciones. Por ejemplo, si el mismo literal de cadena aparece varias veces en el mismo programa (que es bastante común), el compilador puede reutilizar el mismo objeto.
Entonces, ¿por qué se sobrecarga "==" para comparar cadenas de texto? Porque es la semántica más útil. Si dos cadenas son iguales por texto, pueden o no ser la misma referencia de objeto debido a las optimizaciones. Por lo tanto, comparar referencias es bastante inútil, mientras que comparar texto es casi siempre lo que desea.
Hablando de manera más general, Strings tiene lo que se denomina semántica de valor . Este es un concepto más general que los tipos de valor, que es un detalle de implementación específico de C #. Los tipos de valor tienen semántica de valor, pero los tipos de referencia también pueden tener semántica de valor. Cuando un tipo tiene semántica de valor, realmente no se puede saber si la implementación subyacente es un tipo de referencia o un tipo de valor, por lo que puede considerar que es un detalle de implementación.
fuente
string
tipo necesitaría tener un búfer de caracteres de algún tamaño fijo, lo cual sería restrictivo y altamente ineficiente.Esta es una respuesta tardía a una pregunta anterior, pero a todas las demás respuestas les falta el punto, que es que .NET no tenía genéricos hasta .NET 2.0 en 2005.
String
es un tipo de referencia en lugar de un tipo de valor porque era de crucial importancia para Microsoft garantizar que las cadenas se pudieran almacenar de la manera más eficiente en colecciones no genéricas , comoSystem.Collections.ArrayList
.El almacenamiento de un tipo de valor en una colección no genérica requiere una conversión especial al tipo
object
que se llama boxeo. Cuando CLR encuadra un tipo de valor, envuelve el valor dentro deSystem.Object
ay lo almacena en el montón administrado.Leer el valor de la colección requiere la operación inversa que se llama unboxing.
Tanto el boxeo como el unboxing tienen un costo no despreciable: el boxeo requiere una asignación adicional, el unboxing requiere una verificación de tipo.
Algunas respuestas afirman incorrectamente que
string
nunca podrían haberse implementado como un tipo de valor porque su tamaño es variable. En realidad, es fácil implementar cadenas como una estructura de datos de longitud fija utilizando una estrategia de optimización de cadenas pequeñas: las cadenas se almacenarían directamente en la memoria como una secuencia de caracteres Unicode, excepto las cadenas grandes que se almacenarían como un puntero a un búfer externo. Ambas representaciones pueden diseñarse para tener la misma longitud fija, es decir, el tamaño de un puntero.Si los genéricos hubieran existido desde el primer día, supongo que tener una cadena como tipo de valor probablemente hubiera sido una mejor solución, con una semántica más simple, un mejor uso de la memoria y una mejor localidad de caché. Una que
List<string>
contenga solo cadenas pequeñas podría haber sido un solo bloque contiguo de memoria.fuente
string
contiene solo su tamaño y un puntero a lachar
matriz de todos modos, por lo que no sería un "tipo de gran valor". Pero esta es una razón simple y relevante para esta decisión de diseño. ¡Gracias!No solo las cadenas son tipos de referencia inmutables. Delegados multi-elenco también. Por eso es seguro escribir
Supongo que las cadenas son inmutables porque este es el método más seguro para trabajar con ellas y asignar memoria. ¿Por qué no son tipos de valor? Los autores anteriores tienen razón sobre el tamaño de la pila, etc. También agregaría que hacer que las cadenas sean tipos de referencia permiten ahorrar en el tamaño del ensamblaje cuando se usa la misma cadena constante en el programa. Si usted define
Lo más probable es que ambas instancias de la constante "my string" se asignen en su ensamblaje solo una vez.
Si desea administrar cadenas como el tipo de referencia habitual, coloque la cadena dentro de un nuevo StringBuilder (cadena). O use MemoryStreams.
Si va a crear una biblioteca, donde espera que se pasen cadenas enormes en sus funciones, defina un parámetro como StringBuilder o Stream.
fuente
Además, la forma en que se implementan las cadenas (diferentes para cada plataforma) y cuándo comienza a unirlas. Como usar un
StringBuilder
. Le asigna un búfer para que copie, una vez que llegue al final, le asigna aún más memoria, con la esperanza de que si realiza una gran concatenación no se verá obstaculizado.¿Quizás Jon Skeet pueda ayudar aquí?
fuente
Es principalmente un problema de rendimiento.
Hacer que las cadenas se comporten como el tipo de valor LIKE ayuda al escribir código, pero tenerlo como un tipo de valor supondría un gran impacto en el rendimiento.
Para una mirada en profundidad, eche un vistazo a un buen artículo sobre cadenas en el marco .net.
fuente
En palabras muy simples, cualquier valor que tenga un tamaño definido puede tratarse como un tipo de valor.
fuente
¿Cómo puedes saber si
string
es un tipo de referencia? No estoy seguro de que importe cómo se implementa. Las cadenas en C # son inmutables precisamente para que no tenga que preocuparse por este problema.fuente
En realidad, las cadenas tienen muy pocas semejanzas con los tipos de valor. Para empezar, no todos los tipos de valores son inmutables, puede cambiar el valor de un Int32 todo lo que quiera y seguiría siendo la misma dirección en la pila.
Las cadenas son inmutables por una muy buena razón, no tiene nada que ver con que sea un tipo de referencia, pero tiene mucho que ver con la administración de memoria. Es más eficiente crear un nuevo objeto cuando cambia el tamaño de la cadena que cambiar las cosas en el montón administrado. Creo que estás mezclando tipos de valor / referencia y conceptos de objetos inmutables.
En cuanto a "==": Como dijiste, "==" es una sobrecarga del operador, y nuevamente se implementó por una muy buena razón para hacer que el marco sea más útil cuando se trabaja con cadenas.
fuente
No es tan simple como las cadenas están formadas por matrices de caracteres. Miro las cadenas como matrices de caracteres []. Por lo tanto, están en el montón porque la ubicación de la memoria de referencia se almacena en la pila y apunta al comienzo de la ubicación de la memoria de la matriz en el montón. El tamaño de la cadena no se conoce antes de que se asigne ... perfecto para el montón.
Es por eso que una cadena es realmente inmutable porque cuando la cambia, incluso si es del mismo tamaño, el compilador no lo sabe y tiene que asignar una nueva matriz y asignar caracteres a las posiciones en la matriz. Tiene sentido si piensas en las cadenas como una forma en que los lenguajes te protegen de tener que asignar memoria sobre la marcha (lee C como programación)
fuente
A riesgo de obtener otro voto negativo misterioso ... el hecho de que muchos mencionen la pila y la memoria con respecto a los tipos de valor y los tipos primitivos se debe a que deben caber en un registro en el microprocesador. No puede empujar o hacer estallar algo hacia / desde la pila si toma más bits de los que tiene un registro ... las instrucciones son, por ejemplo, "pop eax", porque eax tiene 32 bits de ancho en un sistema de 32 bits.
Los tipos primitivos de punto flotante son manejados por la FPU, que tiene 80 bits de ancho.
Todo esto se decidió mucho antes de que hubiera un lenguaje OOP para ofuscar la definición de tipo primitivo y supongo que tipo de valor es un término que se ha creado específicamente para los lenguajes OOP.
fuente