Las Cadenas De Texto En .NET

2 02 2009

En cada buen libro que he leido sobre .NET Framework o sobre los lenguajes de programación de .NET (C#, VB.NET, J#), siempre hay una sección especialmente dedicada a las cadenas de texto o strings ( System.String). La razón para esto es que esta clase tiene un comportamiento un tanto especial.

1. Tipos Por Valor vs. Tipos Por Referencia

Los String son un tipo por referencia ya que hereda de System.Object. Para poder asignar una cadena de texto a un objeto de tipo System.String se puede usar la siguiente sintaxis simplificada:


string miCadena = "¡Hola Mundo!";

Si intentamos hacer algo como: string miCadena = new string ("¡Hola Mundo!"); no funcionará – en realidad, ni siquiera compilará porque el constructor de la clase string no está sobrecargado para aceptar una cadena de texto como parametro. En vez de eso, otra manera de crear un objeto de tipo string es:


char[] caracteres = {'a','b','c'};
string miCadena = new string(caracteres);
// Creará un objeto de tipo string conteniendo "abc"

Así, si consideramos como es asignado el valor al objeto, system.String podría ser considerado con un tipo por valor.

Otra situación distinta es cuando System.String es un parametro para algún metodo. Los tipos por valor en .NET pueden ser pasados por valor o por referencia (usando la palabra clave ref). En cambio, los tipos por referencia son siempre pasados por referencia. En el código de abajo, el parametro del metodo «ActualizarValor» es un entero (un tipo por valor).


static void Main(string[] args)
{
int miValor = 10;
ActualizarValor(ref miValor);
Console.WriteLine("Mi valor: {0}", miValor);
}
static void ActualizarValor(ref int valor)
{
valor = 20;
}

Si cambiamos el código reemplazando el tipo por valor con el tipo por referencia System.String, obtendremos el mismo resultado: la variable miValor será diferente – la cadena «Nuevo Valor» será mostrada en pantalla -.


static void Main(string[] args)
{
string miValor = "Valor Actual";
ActualizarString(ref miValor);
Console.WriteLine("Mi valor: {0}", miValor);
}
static void ActualizarString(ref string valor)
{
valor = "Nuevo Valor";
}

Si omitimos la palabra clave «ref» de la llamada al método y también de la definición del parametro del método, entonces la salida será el valor contenido en la variable antes de llamar al método: «Valor Actual». Así, la pregunta es por qué System.String es un tipo por referencia cuando se comporta como un tipo por valor. Y la respuesta es un poco más compleja. Muchos lenguajes de programación (incluyendo Java, C#) consideran las cadenas de texto como un tipo primitivo, y por lo tanto el compilador lo trata como tal. Nuevamente, por qué es System.String un tipo por referencia? Principalmente porque hereda de System.Object, y por lo tanto en memoria, existe bajo el Heap (el Heap es el área de la memoria donde son almacenados todos los tipos por referencia) y no bajo el Stack(que es donde todos los tipos por valor van a parar).

2. System.Intern

El CLR (Common Language Runtime) internamente crea una tabla de Hash (también llamada «piscina interna» o «intern pool») donde todos los strings declarados en una aplicación son guardados. El comportamiento de estos strings que son declarados en la aplicación es diferente de una versión a otra del Framework .NET, y por lo tanto no hay que confiar que los strings declarados son automaticamente «internados» (o sea agregados a la tabla de Hash interna). Sin embargo, se puede tener la certeza que todos aquellos para los que el método String.Intertn fue usado están ya agregados a esa tabla.

la clave de la tabla de Hash es el valor del string, y el valor es la referencia al(los) objecto(s) de tipo String. La forma de agregar los strings a la tabla de Hash es mediante una llamada al método System.Intern(string s). Llamando a este método, el CLR revisa si ya hay un string identico en la tabla. Si alguna entrada es encontrada, entonces el método retorna la referencia al string existente. Si ninguna entrada es encontrada, entonces el CLR crea una copia del string la cual es agregada a la tabla de Hash interna y la referencia a la copia es retornada. Otro método relacionado el porceso de internación de los strings es String.IsInterned(string s). Y aunque el nombre del método parece querer decir que el valor de retorno es un valor Booleano, no es así. El tipo que este método retorna es un string, y la lógica es la siguiente: Si el string que está siedo pasado en el método existe en la tabla de Hash interna, entonces el método retorna una referencia a ese objeto. De otro modo, el método retorna null, vale la pena aclarar que el string no es agregado a la tabla de Hash interna si no existe.

También hay una diferencia entre las referencias a objetos de tipo string que existen el Heap y aquellos que están en la tabla de Hash interna de manera que no todos los strings están en la tabla de Hash interna.


string cadena1 = "¡Hola Mundo!";
string cadena2 = "¡Hola Mundo!";
Console.WriteLine("Las refencias son iguales: {0}",
Object.ReferenceEquals(cadena1, cadena2));


string cadena3 = String.Intern("¡Hola Mundo!");
string cadena4 = String.Intern("¡Hola Mundo!");
Console.WriteLine("Las refencias son iguales: {0}",
Object.ReferenceEquals(cadena3, cadena4));

En el primera caso (cadenas 1 y 2), el Framewoek .NET puede agregar los strings a la «piscina interna». Eso dependerá de la versión del framework y también de la configuración/parametros del compilador. Por lo tanto, el resultado puede ser diferente dependiendo de la plataforma y configuraciones. En el segundo caso, sin embargo, el resultado será siempre el mismo: el método ReferenceEquals retorna ‘true’ porque las cadenas de texto son agregadas a la «piscina interna». Hasta ahora, hemos visto que el tipo System.String es un tipo por referencia, y en la segunda situación, cadena3 y cadena4 apuntan a la misma referencia(dirección de memoria en el Heap). La pregunta que puede surgir aquí es: ¿qué pasará si cambio el valor de cadena4, cadena3 cambiará también? Y la respuesta es «NO», para ser más preciso, «No Realmente».

3. Los Strings son Inmutables

En el mundo de la Programación Orientada a Objetos, un objeto es inmutable es aquel que no puede ser modificado una vez que es creado. Este coportamiento de los strings es lo que hace el proceso de internación posible. Teniendo que los strings son inmutables, una copia de la referencia puede ser creada en vez de copiar todo el objeto. Por lo tanto, multiples objetos pueden apuntar a la misma cadena de texto. Pero, la inmutabilidad no significa que la memoria donde el objeto esta almacenado sea sólo de lectura. Lo que realmente significa es que por abajo el Framework .NET se asegura que no se pueda cambiar el valor de la cadena de texto (o al menos no cuando se trabaja con código manejado/código seguro).


Linea 1: string cadena1 = String.Intern("ABC");
Linea 2: string cadena2 = String.Intern("ABC");
Linea 3: cadena2 = cadena2.ToLower();

En la Linea 1 se agrega la cadena «ABC» a la piscina interna, y retorna la referencia al objeto cadena1. En la Linea 2 se intenta agregar la cadena «ABC» a la piscina interna, pero en este caso, de acuerdo con la documentación de .NET, «ABC» no está agregado ya que existe. A su vez, la misma referencia es retornada al objeto cadena2. Hasta ahora, ambos objetos apuntan a la misma cadena ya que apuntan a la misma referencia. Lo más interesante viene en la linea 3. Aquí, el método «ToLower()» hace lo siguiente: crea una nueva cadena y lo pobla con «abc». La referencia a la cadena es retornada, y ahora el objeto cadena2 apunta a una nueva posición de memoria. Hay que notar que no significa que la posición de memoría que contiene la cadena «ABC» fuera sobreescrita con el valor «abc». Por lo tanto, ahora tenemos que cadena1 aun apunta a «ABC» y cadena2 apunta a «abc». Esta presunción es buena y válida cuando estamos en el contexto de código manejado/código seguro. Si estamos trabajando con código no manejado, necesitareos ser muy cuidadosos al hacer operaciones sobre strings. Como mencioné arriba, la posición de memoria donde está almacenada la cadena no es sólo de lectura, y por lo tanto puede ser sobreescrita si escribimos un código que haga eso. Y con el código no manejado, es posible hacerlo.


static void Main(string[] args)
{
string cadena1 = String.Intern("Esto no puede ser cambiado");
string cadena2 = String.Intern("Esto no puede ser cambiado");
int tamañoBuffer = cadena1.Length;
GetUserName(cadena1, ref tamañoBuffer);
Console.WriteLine("La segunda cadena: {0}",cadena2);
}
[DllImport("Advapi32", CharSet = CharSet.Unicode)]
static extern bool GetUserName(
[MarshalAs(UnmanagedType.LPWStr)] string userName, ref int bufferLength);

Si ejecutamos el código de arriba, el siguiente mensaje desplegado en la consola (NombreUsuario es el nombre de usuario en tu PC): «La segunda cadena : NombreUsuario no puede ser cambiado».
la esplicación es, Declaramos cadena1 y cadena2, y nos aseguramos que apunten a la misma cadena usando el método String.Intern(string s). A continuación, un trozo de código no manejado/inseguro es llamado: GetUserName desde «Advapi32.dll». Lo que pasa durante la llamada a este método es la parte interesante: al método se le pasa uno de los strings declarados, y dado que el código no manejado no sigue las reglas del código manejado respetando las reglas de la inmutabilidad de los strings, escribe la respuesta actual a la posición de memoria a la cual apunta cadena1. Pero en el mundo manejado, el objeto cadena2 también apunta a la misma posición, y por lo tanto el contenido de la cadena es cambiado.


Acciones

Information

2 responses

23 05 2010
aleive

Hola que tal, soy Alejandro Solorzano , Te interesa poner anuncios de texto en tus blog.
Puedes ganar hasta 100 Dolares AL MES por cada blog o web.

Le rogamos nos remita los blogs , para poder revisarlos y cualcular el nº de entradas aceptadas.

Alejandro Solorzano

Tel: (503) – 74532917

Msn / Messenger : alejandromd5@hotmail.com

Persona de contacto : Alejandro Solorzano

Saludos cordiales.

6 09 2011
Simser

Der Artikel hat mir sehr gut gefallen, hielt einige neue Infos für mich bereit 🙂

Replica a aleive Cancelar la respuesta