Pretendiendo cadenas de valor son de tipo .NET
-
11-09-2019 - |
Pregunta
En .NET, las cadenas son inmutables y son variables de tipo referencia. A menudo, esto viene como una sorpresa para los nuevos desarrolladores .NET que pueden confundirlos con objetos de tipo de valor debido a su comportamiento. Sin embargo, aparte de la práctica de utilizar StringBuilder
de larga concatenación de esp. en bucles, ¿hay alguna razón en la práctica que se necesita conocer esta distinción?
¿Qué escenarios del mundo real son ayudados o evitarse mediante la comprensión de la diferencia de valor de referencia con respecto a las cadenas de .NET vs fingiendo / malentendido que sean los tipos de valor?
Solución
El diseño de string
s fue deliberadamente tal que no debería tener que preocuparse demasiado acerca de él como programador. En muchas situaciones, esto significa que sólo se puede asignar, mover, copiar, cambiar las cadenas sin pensar demasiado de las posibles consecuencias intrincados si existía otra referencia a la cadena y sería cambiado al mismo tiempo (como ocurre con las referencias a objetos).
parámetros de cadena en una llamada al método
(EDIT: esta sección añadió más tarde)
Cuando las cadenas se pasan a un método, que se pasan por referencia. Cuando sólo se leen en el cuerpo del método, no sucede nada especial. Pero cuando se cambian, se crea una copia y la variable temporal se utiliza en el resto del método. Este proceso se denomina copy-on-write .
Lo que preocupa es que jóvenes que se utilizan para el hecho de que los objetos son referencias y que se cambian en un método que cambia el parámetro pasado. Para hacer lo mismo con las cadenas, tienen que utilizar la palabra clave ref
. En realidad, esto permite que la cadena de referencia para ser transformados y regresó a la función de llamada. Si no lo hace, la cadena no puede ser cambiado por el cuerpo del método:
void ChangeBad(string s) { s = "hello world"; }
void ChangeGood(ref string s) { s = "hello world"; }
// in calling method:
string s1 = "hi";
ChangeBad(s1); // s1 remains "hi" on return, this is often confusing
ChangeGood(ref s1); // s1 changes to "hello world" on return
En StringBuilder
Esta distinción es importante, pero los programadores principiantes suelen ser mejor no saber demasiado acerca de él. Usando StringBuilder
cuando se hace una gran cantidad de "construir" cadena es buena, pero a menudo, su aplicación tendrá mucho más pescado para freír y el pequeño aumento en el rendimiento de StringBuilder
es insignificante. Tenga cuidado con los programadores que le indican que todos manipulación de cadenas se debe hacer uso de StringBuilder.
Como regla empírica aproximada: StringBuilder tiene algún costo creación, pero Anexión es barato. Cadena tiene un costo creación barato, pero la concatenación es relativamente caro. El punto de inflexión es de alrededor de 400-500 concatenaciones, dependiendo del tamaño: después de eso, StringBuilder se vuelve más eficiente
.Más información sobre el rendimiento StringBuilder vs cadena
EDIT: basado en un comentario de Konrad Rudolph, añadí esta sección
.Si la regla anterior del pulgar hace que uno se pregunta, considere los siguientes explicaciones un poco más detalladas:
- StringBuilder con muchos pequeños cadena añade la concatenación de cadenas sobrepasa con bastante rapidez (30, 50 APPENDs), pero en 2μs, incluso el aumento de rendimiento de 100% es a menudo insignificante (salvo para algunas situaciones poco frecuentes);
- StringBuilder con algunos grandes APPENDs de cuerda (80 caracteres o cadenas más grandes) deja atrás a la concatenación de cadenas sólo después de miles, a veces centésimas de miles de iteraciones y la diferencia es a menudo sólo unos porcentajes;
- Mezcla acciones de cuerda (reemplazar, insertar, en cadenas, la expresión regular, etc.) a menudo hace que el uso de StringBuilder o la concatenación de cadenas iguales;
- concatenación de cadenas de constantes se puede optimizar de distancia por el compilador, el CLR o el JIT, no pueda por StringBuilder;
- Código menudo mezcla
+
concatenación,StringBuilder.Append
,String.Format
,ToString
y otras operaciones de cadena, utilizando StringBuilder en estos casos casi nunca es eficaz.
Por lo tanto, cuando es es eficiente? En los casos en que se añaden muchas pequeñas cadenas, es decir, para serializar los datos en un archivo, por ejemplo, y cuando no es necesario cambiar los datos "escrito" una vez "por escrito" a StringBuilder. Y en los casos en que muchos métodos necesitan para añadir algo, porque StringBuilder es un tipo de referencia y las cuerdas se copian cuando se cambian.
En las cadenas internadas
Un problema surge - no sólo con los programadores Junior - cuando tratan de hacer una comparación de referencia y descubrir que a veces el resultado es verdadero, y, a veces es falso, aparentemente en las mismas situaciones. ¿Que pasó? Cuando las cuerdas fueron internados por el compilador y se añaden a la estática mundial internados conjunto de cadenas, la comparación entredos cuerdas pueden apuntar a la misma dirección de memoria. Cuando (referencia!) Comparar dos cadenas iguales, una internada y uno no, dará lugar a falsas. Usar la comparación =
o Equals
y no jugar con ReferenceEquals
cuando se trata de cadenas.
En String.Empty
En la misma liga se ajusta a un comportamiento extraño que a veces ocurre cuando se utiliza String.Empty
: la String.Empty
estática siempre es internado, pero una variable con un valor asignado no lo es. Sin embargo, por defecto el compilador asignará String.Empty
y apuntan a la misma dirección de memoria. Resultado:. Una variable de cadena mutable, cuando se compara con ReferenceEquals
, devuelve verdadero, mientras que se podría esperar falsa en lugar
// emptiness is treated differently:
string empty1 = String.Empty;
string empty2 = "";
string nonEmpty1 = "something";
string nonEmpty2 = "something";
// yields false (debug) true (release)
bool compareNonEmpty = object.ReferenceEquals(nonEmpty1, nonEmpty2);
// yields true (debug) false (release, depends on .NET version and how it's assigned)
bool compareEmpty = object.ReferenceEquals(empty1, empty2);
En profundidad
Es, básicamente, preguntó acerca de las situaciones que pueden ocurrir a los no iniciados. Creo que mi punto se reduce a evitar object.ReferenceEquals
, ya que no se puede confiar cuando se utiliza con cuerdas. La razón es que la internación cadena se utiliza cuando la cadena es constante en el código, pero no siempre. No se puede confiar en este comportamiento. Aunque String.Empty
y ""
siempre están internados, no es cuando el compilador cree que el valor se puede cambiar. Diferentes opciones de optimización de depuración (vs liberación y otros) van a dar resultados diferentes.
Cuando lo que necesita ReferenceEquals
de todos modos? Con los objetos que tiene sentido, pero con condiciones que no lo hace. Enseñar a nadie a trabajar con cuerdas para evitar su uso a menos que también entienden unsafe
y cubrió a objetos.
Rendimiento
Cuando el rendimiento es importante, se puede saber que las cadenas son en realidad no inmutable y que usando StringBuilder
es no siempre el más rápido enfoque .
Una gran cantidad de la información que se utiliza aquí es noreferrer">
Actualización: añadido código de ejemplo
Actualización: añade 'en profundidad' sección (espero que alguien encuentre esto útil;)
Actualización: añade algunos enlaces, sección de cuerda params
añadió
Actualización: añade la estimación de cuándo cambiar de cuerdas para StringBuilder
Actualización: añade una sección adicional en el rendimiento vs StringBuilder String, después de una observación de Konrad Rudolph
Otros consejos
La única distinción que realmente importa para la mayoría del código es el hecho de que null
se puede asignar a las variables de cadena.
Una clase inmutable actúa como un tipo de valor en todas las situaciones comunes, y se puede hacer un buen montón de programación sin preocuparse demasiado acerca de la diferencia.
Es cuando se profundiza un poco más y se preocupan por el rendimiento que tiene el uso real para la distinción. Por ejemplo, para saber que a pesar de que pasa una cadena como un parámetro a un método actúa como si se crea una copia de la cadena, la copia en realidad no tiene lugar. Esto podría ser una sorpresa para la gente que se utilizan para las lenguas en las cadenas de hecho son los tipos de valor (como Visual Basic 6?), Y pasando una gran cantidad de cadenas como parámetros no sería bueno para el rendimiento.
La cadena es una raza especial. Son del tipo de referencia utilizado todavía por la mayoría de los codificadores como un tipo de valor. Por lo que es inmutable y el uso de la piscina interno, que optimiza el uso de memoria que será enorme si se trata de un tipo de valor puro.
Más lecturas aquí: Read
C # .NET Cadena objeto es realmente por referencia? el SO
String.Intern Método en MSDN
cadena (Referencia de C #) en MSDN
Actualización:
Por favor refiérase al comentario de abel
a este mensaje. Se corrigió mi declaración engañosa.