La máxima que encabeza este artículo es bien conocida en la naturaleza. Lo que ocurre es que en .NET hay algunas clases que parece que actúen contra-natura. Bromas aparte me gustaría hacer mención aquí a un comportamiento de las cadenas de texto en .NET que, si bien bastante lógico, puede que despiste a más de uno si no se lo han hecho notar nunca.
Consideremos el siguiente código, tan sencillo:
MiClase c1 = new MiClase();
MiClase c2 = c1;
Cuando en .NET hacemos esto la variable 'c2' contendrá una referencia a 'c1'. Si ahora, por el motivo que sea, transformamos los datos encapsulados en el objeto 'c1' o incluso si cambiamos el objeto al que referencia esta variable, 'c2' seguirá siendo idéntica a 'c1', es decir seguirá referenciando el mismo objeto al que referencie a su vez 'c1'.
Sin embargo hagamos algo similar con una cadena de texto:
string s1 = "Hola Pepe";
string s2 = s1;
s1 = s1.Replace("Pepe", "Paco");
Console.WriteLine(s1);
Console.WriteLine(s2);
Console.ReadLine();
Según lo que acabamos de explicar en el párrafo anterior, al terminar de ejecutarse este código deberíamos ver escrita en la consola dos veces la frase "Hola Paco", ya que 's1' ha cambiado y 's2' referencia a 's1'. Sin embargo lo que veremos es "Hola Paco" y luego "Hola Pepe", es decir, que aparentemente la teoría ha fallado. ¿A qué es debido este comportamiento "extraño"?
La clase 'string' de .NET es algo peculiar ya que se comporta al mismo tiempo como una clase y como un valor nativo. Su mayor peculiaridad es que se trata de un tipo Inmutable. Esto quiere decir que una vez establecido el valor de una cadena éste no se puede cambiar en modo alguno. Cualquier cosa que hagamos que modifique una cadena (como cambiar parte de ésta como en el ejemplo, o concatenarle otra, etc...) lo que en realidad hace es que se genere una nueva cadena desechando la anterior, de ahí el comportamiento observado.
Y esto ¿por qué hacerlo así?
Bueno, si lo pensamos con calma veremos que es bastante lógico que así sea. Para empezar, cuanod asignamos una cadena a una variable no estamos pensando en ella como un objeto al uso sino más bien como un valor. Al hacer la segunda asignación lo normal es que se cree una copia puesto que de no ser así ambas variables estarían trabajando sobre la misma cadena y sería fuente de múltiples errores normalmente al no tratar a éstas como objetos.
Además de este motivo más filosófico existen otros de índole más práctica. El funcionamiento interno de la cadena se simplifica enormemente si se parte de la base de que ésta es inmutable. No hay que mantener una matriz dinámica u otra estructura cambiante en su seno para guardar los datos, no hay que meter coplejos algoritmos para conservar sus partes o tratarlas en modo alguno, no hay que preocuparse en realidad por demasiadas cosas: solamente se crea una nueva instancia y se devuelve, lo cual es muy eficiente.
Como contrapartida se gasta mucha más memoria (mientras no actúe el recolector de basura al menos). Es por esto que cuando se deba modificar continuamente una cadena (por ejemplo dentro de un bucle concatenándole nuevas subcadenas) es muchísimo más eficiente y rápido utilizar la clase StringBuilder, que está optimizada para almacenar cada fragmento que entre en juego en memoria y, sólo al final cuando la necsitemos generar una nueva cadena. De hecho este es el comportamiento que se usaba también en Visual Basic o en los lenguajes de Script. En nuestra empresa, muchos años antes de .NET, ya disponíamos de una clase StringBuilder propia que nos permitía trabajar concatenando grandes cadenas a una velocidad elevadísima.
En resumen
Dos ideas fundamentales:
1.- Cuidado con código similar al del ejemplo de este texto. Debe darse cuenta de que al modificar una cadena de algún modo la perderá para siempre.
2.- Si necesita generar grandes cadenas a partir de otras mucho menores o si necesita trabajar con cadenas aparentemente "Mutables" emplee la magnífica clase StringBuilder.
No por ser conceptos básicos los tenemos siempre presentes y, como me he encontrado con esta situación hace poco me he animado a recordarla aquí.