¿Por qué .NET String es inmutable?

Como todos sabemos, String es inmutable. ¿Cuáles son las razones por las cuales la cadena es inmutable y la introducción de la clase StringBuilder como mutable?

    1. Las instancias de tipos inmutables son intrínsecamente seguras para subprocesos, ya que ningún subproceso puede modificarlo, se elimina el riesgo de que un subproceso lo modifique de una manera que interfiera con otro (la referencia en sí es una cuestión diferente).
    2. De manera similar, el hecho de que el aliasing no puede producir cambios (si xey se refieren al mismo objeto un cambio en x implica un cambio a y) permite una considerable optimización del comstackdor.
    3. Las optimizaciones de ahorro de memoria también son posibles. Internar y atomizar son los ejemplos más obvios, aunque podemos hacer otras versiones del mismo principio. Una vez producí un ahorro de memoria de aproximadamente medio GB al comparar objetos inmutables y reemplazar referencias a duplicados para que todos apuntasen a la misma instancia (consumía mucho tiempo, pero un arranque adicional de un minuto para guardar una gran cantidad de memoria era una rendimiento ganado en el caso en cuestión). Con objetos mutables que no se pueden hacer.
    4. No se pueden producir efectos secundarios al pasar un tipo inmutable como método a un parámetro a menos que sea out o ref (ya que eso cambia la referencia, no el objeto). Por lo tanto, un progtwigdor sabe que si la string x = "abc" al comienzo de un método, y eso no cambia en el cuerpo del método, entonces x == "abc" al final del método.
    5. Conceptualmente, la semántica se parece más a los tipos de valor; en particular, la igualdad se basa en el estado en lugar de la identidad. Esto significa que "abc" == "ab" + "c" . Si bien esto no requiere inmutabilidad, el hecho de que una referencia a dicha cadena siempre será igual a “abc” durante toda su vida (que requiere inmutabilidad) hace que los usos como claves donde el mantenimiento de la igualdad a los valores anteriores es vital, mucho más fácil para garantizar la corrección de (las cuerdas son comúnmente usadas como llaves).
    6. Conceptualmente, puede tener más sentido ser inmutable. Si agregamos un mes a la Navidad, no hemos cambiado la Navidad, hemos producido una nueva fecha a fines de enero. Por lo tanto, tiene sentido que Christmas.AddMonths(1) produzca un nuevo DateTime lugar de cambiar uno mutable. (Otro ejemplo, si yo, como objeto mutable, cambio mi nombre, lo que ha cambiado es el nombre que estoy usando, “Jon” permanece inmutable y otros Jons no se verán afectados).
    7. Copiar es rápido y simple, para crear un clon simplemente return this . Como la copia no se puede cambiar de todos modos, pretender que algo es su propia copia es seguro.
    8. [Editar, me había olvidado de esto]. El estado interno se puede compartir de forma segura entre los objetos. Por ejemplo, si estaba implementando una lista respaldada por una matriz, un índice de inicio y un recuento, entonces la parte más costosa de crear un subintervalo sería copiar los objetos. Sin embargo, si era inmutable, el objeto de rango inferior podría hacer referencia a la misma matriz, teniendo que cambiar solo el índice de inicio y el recuento, con un cambio considerable en el tiempo de construcción.

    En total, para los objetos que no experimentan cambios como parte de su propósito, puede haber muchas ventajas en ser inmutable. La principal desventaja es que requiere construcciones adicionales, aunque incluso en este caso a menudo se exagera (recuerde que debe hacer varias adiciones antes de que StringBuilder se vuelva más eficiente que la serie equivalente de concatenaciones, con su construcción inherente).

    Sería una desventaja si la mutabilidad fuera parte del propósito de un objeto (quién quisiera ser modelado por un objeto Empleado cuyo salario nunca podría cambiar) aunque a veces incluso entonces puede ser útil (en muchos sitios web y otros sin estado). las aplicaciones, el código que realiza operaciones de lectura está separado de las actualizaciones, y el uso de diferentes objetos puede ser natural: no convertiría un objeto inmutable y forzaría ese patrón, pero si ya tuviera ese patrón podría hacer que mis objetos de “lectura” inmutable por el rendimiento y la ganancia de garantía de corrección).

    Copiar-escribir es un término medio. Aquí la clase “real” contiene una referencia a una clase “estatal”. Las clases de estado se comparten en las operaciones de copia, pero si cambia el estado, se crea una nueva copia de la clase de estado. Esto se usa más a menudo con C ++ que con C #, razón por la cual es std: string disfruta de algunas, pero no de todas, las ventajas de los tipos inmutables, sin dejar de ser mutable.

    Hacer cadenas inmutables tiene muchas ventajas. Proporciona seguridad automática de subprocesos y hace que las cadenas se comporten como un tipo intrínseco de una manera simple y efectiva. También permite eficiencias adicionales en el tiempo de ejecución (por ejemplo, permite el interceso efectivo de cadenas para reducir el uso de recursos) y tiene enormes ventajas de seguridad, ya que es imposible que una llamada de API de terceros cambie sus cadenas de caracteres.

    StringBuilder se agregó para abordar la principal desventaja de las cadenas inmutables: la construcción en tiempo de ejecución de tipos inmutables causa una gran cantidad de presión de GC y es inherentemente lenta. Al hacer una clase explícita y mutable para manejar esto, este problema se aborda sin agregar complicación innecesaria a la clase de cadena.

    Las cadenas no son realmente inmutables. Ellos son solo públicamente inmutables. Significa que no puedes modificarlos desde su interfaz pública. Pero en el interior son realmente mutables.

    Si no me crees, mira la definición de String.Concat usando el reflector . Las últimas líneas son …

     int length = str0.Length; string dest = FastAllocateString(length + str1.Length); FillStringChecked(dest, 0, str0); FillStringChecked(dest, length, str1); return dest; 

    Como puede ver, FastAllocateString devuelve una cadena vacía pero asignada y luego es modificada por FillStringChecked

    En realidad, FastAllocateString es un método externo y FillStringChecked no es seguro, por lo que utiliza punteros para copiar los bytes.

    Tal vez hay mejores ejemplos, pero este es el que he encontrado hasta ahora.

    la administración de cadenas es un proceso costoso. mantener cadenas inmutables permite reutilizar cadenas repetidas, en lugar de volver a crearlas.

    ¿Por qué los tipos de cadena son inmutables en C #

    String es un tipo de referencia, por lo que nunca se copia, pero se pasa por referencia. Compare esto con el objeto C ++ std :: string (que no es inmutable), que se pasa por valor. Esto significa que si quieres usar un String como clave en un Hashtable, estás bien en C ++, porque C ++ copiará el string para almacenar la clave en el hashtable (en realidad std :: hash_map, pero aún) para una comparación posterior . Entonces, incluso si luego modificas la instancia de std :: string, estás bien. Pero en .Net, cuando usa un String en una Hashtable, almacenará una referencia a esa instancia. Ahora suponga por un momento que las cadenas no son inmutables, y vea qué sucede: 1. Alguien inserta un valor x con la tecla “hola” en una tabla Hashtable. 2. La Hashtable calcula el valor hash de la cadena y coloca una referencia a la cadena y el valor x en la categoría correspondiente. 3. El usuario modifica la instancia de String para que sea “bye”. 4. Ahora alguien quiere el valor en la tabla hash asociada con “hola”. Termina buscando en el cubo correcto, pero al comparar las cadenas dice “¡adiós!” = “Hola”, por lo que no se devuelve ningún valor. 5. ¿Tal vez alguien quiere el valor “adiós”? “Adiós” probablemente tenga un hash diferente, por lo que la tabla de hash se verá en un cubo diferente. No hay claves de “adiós” en ese cubo, por lo que todavía no se encuentra nuestra entrada.

    Hacer cadenas inmutables significa que el paso 3 es imposible. Si alguien modifica la cadena, está creando un nuevo objeto de cadena, dejando solo el anterior. Lo que significa que la clave en la tabla hash sigue siendo “hola”, y por lo tanto sigue siendo correcta.

    Por lo tanto, probablemente entre otras cosas, las cadenas inmutables son una forma de permitir que las cadenas que se pasan por referencia se utilicen como claves en una tabla hash u objeto de diccionario similar.

    Nunca tienes que copiar a la defensiva datos inmutables. A pesar de que necesita copiarlo para mutarlo, a menudo la capacidad de alias libremente y nunca tener que preocuparse por las consecuencias involuntarias de este aliasing puede llevar a un mejor rendimiento debido a la falta de copias defensivas.

    Solo para arrojar esto, una visión que a menudo se olvida es de seguridad, imagine este escenario si las cadenas fueran mutables:

     string dir = "C:\SomePlainFolder"; //Kick off another thread GetDirectoryContents(dir); void GetDirectoryContents(string directory) { if(HasAccess(directory) { //Here the other thread changed the string to "C:\AllYourPasswords\" return Contents(directory); } return null; } 

    Usted ve cómo podría ser muy, muy malo si se le permitiera mutar las cuerdas una vez que se pasaron.

    Las cadenas y otros objetos concretos se expresan típicamente como objetos inmutables para mejorar la legibilidad y la eficacia del tiempo de ejecución. La seguridad es otra, un proceso no puede cambiar su cadena e inyectar código en la cadena

    Las cadenas se pasan como tipos de referencia en .NET.

    Los tipos de referencia colocan un puntero en la stack, a la instancia real que reside en el montón administrado. Esto es diferente a los tipos de valor, que mantienen su instancia completa en la stack.

    Cuando se pasa un tipo de valor como parámetro, el tiempo de ejecución crea una copia del valor en la stack y pasa ese valor a un método. Es por esto que los enteros se deben pasar con una palabra clave ‘ref’ para devolver un valor actualizado.

    Cuando se pasa un tipo de referencia, el tiempo de ejecución crea una copia del puntero en la stack. Ese puntero copiado aún apunta a la instancia original del tipo de referencia.

    El tipo de cadena tiene un operador = sobrecargado que crea una copia de sí mismo, en lugar de una copia del puntero, lo que hace que se comporte más como un tipo de valor. Sin embargo, si solo se copiara el puntero, una segunda operación de cadena podría sobrescribir accidentalmente el valor de un miembro privado de otra clase, causando algunos resultados desagradables.

    Como han mencionado otras publicaciones, la clase StringBuilder permite la creación de cadenas sin la sobrecarga del GC.

    Imagine que pasa una cadena mutable a una función, pero no espere que se modifique. Entonces, ¿qué pasa si la función cambia esa cadena? En C ++, por ejemplo, simplemente puedes hacer call-by-value (diferencia entre std::string y std::string& parameter), pero en C # se trata de referencias, por lo que si pasas cadenas mutables alrededor de cada función podría cambiarlo y desencadenar efectos secundarios inesperados.

    Esta es solo una de varias razones. El rendimiento es otro (cadenas internas, por ejemplo).

    Hay cinco maneras comunes por las cuales un dato del almacén de datos de clase que no se puede modificar fuera del control de la clase de almacenamiento:

    1. Como primitivas de tipo valor
    2. Al mantener una referencia libremente compartible del objeto de clase cuyas propiedades de interés son todas inmutables
    3. Al mantener una referencia a un objeto de clase mutable que nunca estará expuesto a nada que pueda mutar cualquier propiedad de interés
    4. Como una estructura, ya sea “mutable” o “inmutable”, todos sus campos son de tipos # 1 a # 4 (no # 5).
    5. Al mantener la única copia existente de una referencia a un objeto cuyas propiedades solo pueden mutarse a través de esa referencia.

    Como las cadenas son de longitud variable, no pueden ser primitivas de tipo valor, ni sus datos de caracteres se pueden almacenar en una estructura. Entre las opciones restantes, la única que no requeriría que los datos de caracteres de las cadenas se almacenaran en algún tipo de objeto inmutable sería # 5. Si bien sería posible diseñar un marco alrededor de la opción n. ° 5, esa elección requeriría que cualquier código que deseara una copia de una cadena que no podría cambiarse fuera de su control tendría que hacer una copia privada para sí mismo. Aunque difícilmente es imposible hacerlo, la cantidad de código adicional requerido para hacer eso, y la cantidad de procesamiento adicional en tiempo de ejecución necesario para hacer copias defensivas de todo, superarían con creces los leves beneficios que podrían obtenerse al ser mutable la string , especialmente dado que hay un tipo de cadena mutable ( System.Text.StringBuilder ) que logra el 99% de lo que se puede lograr con una string mutable.

    Las cadenas inmutables también evitan problemas relacionados con la concurrencia.

    Imagine que es un SO que trabaja con una cadena que algún otro hilo estaba modificando a sus espaldas. ¿Cómo podría validar algo sin hacer una copia?