Cuando paso una string
a una función, ¿se pasa un puntero al contenido de la cadena o se pasa toda la cadena a la función en la pila como lo struct
haría a?
Cuando paso una string
a una función, ¿se pasa un puntero al contenido de la cadena o se pasa toda la cadena a la función en la pila como lo struct
haría a?
Respuestas:
Se pasa una referencia; sin embargo, técnicamente no se pasa por referencia. Ésta es una distinción sutil, pero muy importante. Considere el siguiente código:
void DoSomething(string strLocal)
{
strLocal = "local";
}
void Main()
{
string strMain = "main";
DoSomething(strMain);
Console.WriteLine(strMain); // What gets printed?
}
Hay tres cosas que necesita saber para comprender lo que sucede aquí:
strMain
no se pasa por referencia. Es un tipo de referencia, pero la referencia en sí se pasa por valor . Cada vez que pasa un parámetro sin la ref
palabra clave (sin contar los out
parámetros), pasa algo por valor.Eso debe significar que estás ... pasando una referencia por valor. Dado que es un tipo de referencia, solo se copió la referencia en la pila. Pero ¿qué significa eso?
Las variables de C # son tipos de referencia o tipos de valor . Los parámetros de C # se pasan por referencia o por valor . La terminología es un problema aquí; estos suenan como lo mismo, pero no lo son.
Si pasa un parámetro de CUALQUIER tipo y no usa la ref
palabra clave, entonces 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 lo que copió fue la referencia, no lo que apuntaba.
Aquí está la primera línea del Main
método:
string strMain = "main";
Hemos creado dos cosas en esta línea: una cadena con el valor main
almacenado en la memoria en algún lugar, y una variable de referencia llamada strMain
apuntando hacia ella.
DoSomething(strMain);
Ahora pasamos esa referencia a DoSomething
. Lo pasamos por valor, lo que significa que hicimos una copia. Es un tipo de referencia, lo que significa que copiamos la referencia, no la cadena en sí. Ahora tenemos dos referencias que cada una apunta al mismo valor en la memoria.
Aquí está la parte superior del DoSomething
método:
void DoSomething(string strLocal)
Sin ref
palabra clave, entonces strLocal
y strMain
son dos referencias diferentes que apuntan al mismo valor. Si reasignamos strLocal
...
strLocal = "local";
... no hemos cambiado el valor almacenado; Tomamos la referencia llamada strLocal
y la apuntamos a una cuerda nueva. ¿Qué pasa con strMain
cuando hacemos eso? Nada. Todavía apunta a la vieja cuerda.
string strMain = "main"; // Store a string, create a reference to it
DoSomething(strMain); // Reference gets copied, copy gets re-pointed
Console.WriteLine(strMain); // The original string is still "main"
Cambiemos el escenario por un segundo. Imagina que no estamos trabajando con cadenas, sino con algún tipo de referencia mutable, como una clase que has 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 solo uno MutableThing
en la memoria, y tanto la referencia copiada como la referencia original todavía apuntan a él. Las propiedades del MutableThing
mismo han cambiado :
void Main()
{
var objMain = new MutableThing();
objMain.ChangeMe = 5;
Console.WriteLine(objMain.ChangeMe); // it's 5 on objMain
DoSomething(objMain); // now it's 0 on objLocal
Console.WriteLine(objMain.ChangeMe); // it's also 0 on objMain
}
¡Ah, pero las cuerdas son inmutables! No hay ninguna ChangeMe
propiedad que configurar. No puede hacer strLocal[3] = 'H'
en C # como podría hacerlo con una char
matriz de estilo C ; tienes que construir una cadena completamente nueva en su lugar. La única forma de cambiar strLocal
es apuntar la referencia a otra cadena, y eso significa que nada de lo que haga strLocal
puede afectar strMain
. El valor es inmutable y la referencia es una copia.
Para demostrar que hay una diferencia, esto es lo que sucede cuando pasa una referencia por referencia:
void DoSomethingByReference(ref string strLocal)
{
strLocal = "local";
}
void Main()
{
string strMain = "main";
DoSomethingByReference(ref strMain);
Console.WriteLine(strMain); // Prints "local"
}
Esta vez, la cadena en Main
realmente se cambia porque pasó la referencia sin copiarla en la pila.
Entonces, aunque las cadenas son tipos de referencia, pasarlas por valor significa que lo que suceda en el destinatario de la llamada no afectará a la cadena en el llamador. Pero como son tipos de referencia, no es necesario que copie la cadena completa en la memoria cuando quiera pasarla.
ref
palabra clave. Para demostrar que pasar por referencia marca la diferencia, vea esta demostración: rextester.com/WKBG5978
ref
palabra clave tiene utilidad, solo estaba tratando de explicar por qué uno podría pensar en pasar un tipo de referencia por valor en C # parece la noción "tradicional" (es decir, C) de pasar por referencia (y pasar un tipo de referencia por referencia en C # parece más como pasar una referencia a una referencia por valor).
Foo(string bar)
podría ser pensado como Foo(char* bar)
mientras que Foo(ref string bar)
sería Foo(char** bar)
(o Foo(char*& bar)
, o Foo(string& bar)
en C ++). Claro, no es como deberías pensarlo todos los días, pero en realidad me ayudó a comprender finalmente lo que está sucediendo bajo el capó.
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 se puede modificar. 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.
Entonces, 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.
Uri
(clase) y Guid
(estructura) también son casos especiales. No veo cómo System.String
actúa como un "tipo de valor" más que otros tipos inmutables ... de orígenes de clase o estructura.
Uri
& Guid
, solo puede asignar un valor literal de cadena a una variable de cadena. La cadena parece ser mutable, como una int
reasignación, pero está creando un objeto implícitamente, sin new
palabra clave.