¿Cómo se pasan las cadenas en .NET?

Cuando paso una string a una función, ¿se pasa un puntero al contenido de la cadena, o la cadena completa pasa a la función en la stack como lo haría una struct ?

Para responder a su pregunta, considere el siguiente código:

 void Main() { string strMain = "main"; DoSomething(strMain); Console.Write(strMain); // What gets printed? } void DoSomething(string strLocal) { strLocal = "local"; } 

Hay tres cosas que necesita saber para predecir lo que sucederá aquí y comprender por qué lo hace.

  1. Las cadenas son tipos de referencia en C #. Pero esto es solo una parte de la imagen.
  2. También son inmutables, por lo que cada vez que haces algo que parece que estás cambiando la cuerda, no lo eres. Se crea una cadena completamente nueva, se apunta a la referencia y la anterior se descarta.
  3. Aunque las cadenas son tipos de referencia, strMain no se pasa por referencia. Es un tipo de referencia, pero la referencia se está pasando por valor . Esta es una distinción difícil, pero es crucial. Cada vez que pasa un parámetro sin la palabra clave ref (sin contar out parámetros), ha pasado algo por valor.

Pero ¿qué significa eso?

Pasar tipos de referencia por valor: ya lo estás haciendo

Hay dos grupos de tipos de datos en C #: tipos de referencia y tipos de valores . También hay dos formas de pasar parámetros en C #: por referencia y por valor . Estos suenan igual y se confunden fácilmente. ¡No són la misma cosa!

Si pasa un parámetro de CUALQUIER tipo y no usa la palabra clave ref , lo ha pasado por valor. Si lo pasó por valor, lo que realmente aprobó fue una copia. Pero si el parámetro era un tipo de referencia, entonces la cosa que copiaba era la referencia, no lo que apuntaba.

Aquí está la primera línea de nuestro método Main :

 string strMain = "main"; 

En realidad, hay dos cosas que hemos creado en esta línea: una cadena con el valor main almacenado en la memoria en alguna parte, y una variable de referencia llamada strMain apunta a ella.

 DoSomething(strMain); 

Ahora pasamos esa referencia a DoSomething . Lo hemos pasado por valor, así que eso significa que hicimos una copia. Pero es un tipo de referencia, por lo que significa que hemos copiado la referencia, no la cadena en sí. Ahora tenemos dos referencias que apuntan al mismo valor en la memoria.

Dentro del callee

Aquí está la parte superior del método DoSomething :

 void DoSomething(string strLocal) 

Sin palabra clave ref , como es habitual. Así que strLocal no es strMain , pero ambos apuntan al mismo lugar. Si “cambiamos” strLocal , así …

 strLocal = "local"; 

… no hemos cambiado el valor almacenado, per se. Hemos reenfocado la referencia. Tomamos la referencia llamada strLocal y la strLocal a una nueva cadena. ¿Qué pasa con strMain cuando hacemos eso? Nada. ¡Sigue apuntando a la cuerda vieja!

 string strMain = "main"; //Store a string, create a reference to it DoSomething(strMain); //Reference gets copied, copy gets re-pointed Console.Write(strMain); //The original string is still "main" 

La inmutabilidad es importante

Cambiemos el escenario por un segundo. Imagine que no estamos trabajando con cadenas, sino con algún tipo de referencia mutable, como una clase que ha creado.

 class MutableThing { public int ChangeMe { get; set; } } 

Si sigue la referencia objLocal al objeto al que apunta, puede cambiar sus propiedades:

 void DoSomething(MutableThing objLocal) { objLocal.ChangeMe = 0; } 

Todavía hay un solo MutableThing en la memoria, y tanto la referencia copiada como la referencia original aún apuntan a ella. Las propiedades de MutableThing sí han cambiado :

 void Main() { var objMain = new MutableThing(); objMain.ChangeMe = 5; Console.Write(objMain.ChangeMe); //it's 5 on objMain DoSomething(objMain); //now it's 0 on objLocal Console.Write(objMain.ChangeMe); //it's also 0 on objMain } 

Ah, pero …

… las cadenas son inmutables! No hay propiedad ChangeMe para establecer. No se puede hacer strLocal[3] = 'H'; como podrías con una matriz de caracteres C-style; tienes que construir una nueva cadena en su lugar. La única forma de cambiar strLocal es apuntar la referencia a otra cadena, y eso significa que nada de lo que hagas a strLocal puede afectar a strMain . El valor es inmutable, y la referencia es una copia.

Entonces, aunque las cadenas son tipos de referencia, pasarlas por valor significa que todo lo que sucede en el destinatario no afectará a la cadena en la persona que llama. Pero dado que son tipos de referencia, no tiene que copiar toda la cadena en la memoria cuando quiere pasarla.

Recursos adicionales:

  • Este es el mejor artículo que he leído sobre la diferencia entre los tipos de referencia y los tipos de valores en C # , y por qué un tipo de referencia no es lo mismo que un parámetro de referencia pasada.
  • Como de costumbre, Eric Lippert también tiene varias publicaciones excelentes en el blog sobre el tema .
  • Él tiene algunas cosas buenas sobre la inmutabilidad , también.

Las cadenas en C # son objetos de referencia inmutables. Esto significa que las referencias a ellos se pasan (por valor), y una vez que se crea una cadena, no puede modificarla. Los métodos que producen versiones modificadas de la cadena (subcadenas, versiones recortadas, etc.) crean copias modificadas de la cadena original.

Las cadenas son casos especiales. Cada instancia es inmutable. Cuando cambia el valor de una cadena, está asignando una nueva cadena en la memoria.

Por lo tanto, solo se pasa la referencia a su función, pero cuando se edita la cadena, se convierte en una nueva instancia y no modifica la instancia anterior.