una - C#tipo de referencia de cadena?
tipos de datos en c# pdf (8)
Como otros han indicado, el tipo String
en .NET es inmutable y su referencia se pasa por valor.
En el código original, tan pronto como esta línea se ejecuta:
test = "after passing";
entonces la test
ya no se refiere al objeto original. Creamos un nuevo objeto String
y una test
asignada para hacer referencia a ese objeto en el montón administrado.
Siento que mucha gente se tropieza aquí porque no hay un constructor formal visible que se lo recuerde. En este caso, está sucediendo detrás de las escenas ya que el tipo String
tiene soporte de lenguaje en cómo se construye.
Por lo tanto, esta es la razón por la cual el cambio a la test
no es visible fuera del alcance del método TestI(string)
: ¡hemos pasado la referencia por valor y ahora ese valor ha cambiado! Pero si la referencia de String
se pasó por referencia, cuando cambie la referencia la veremos fuera del alcance del método TestI(string)
.
En este caso, se necesita la palabra clave ref o out . Siento que la palabra clave out
podría ser un poco más adecuada para esta situación en particular.
class Program
{
static void Main(string[] args)
{
string test = "before passing";
Console.WriteLine(test);
TestI(out test);
Console.WriteLine(test);
Console.ReadLine();
}
public static void TestI(out string test)
{
test = "after passing";
}
}
Sé que "cadena" en C # es un tipo de referencia. Esto está en MSDN. Sin embargo, este código no funciona como debería:
class Test
{
public static void Main()
{
string test = "before passing";
Console.WriteLine(test);
TestI(test);
Console.WriteLine(test);
}
public static void TestI(string test)
{
test = "after passing";
}
}
La salida debe ser "antes de pasar" "después de pasar", ya que estoy pasando la cadena como un parámetro y, como es un tipo de referencia, la segunda declaración de salida debe reconocer que el texto cambió en el método TestI. Sin embargo, recibo "antes de aprobar" "antes de aprobar", lo que hace que parezca que se pasa por valor no por ref. Entiendo que las cadenas son inmutables, pero no veo cómo eso explicaría lo que está sucediendo aquí. ¿Qué me estoy perdiendo? Gracias.
Creo que su código es análogo al siguiente, y no debería haber esperado que el valor haya cambiado por la misma razón por la que no estaría aquí:
public static void Main()
{
StringWrapper testVariable = new StringWrapper("before passing");
Console.WriteLine(testVariable);
TestI(testVariable);
Console.WriteLine(testVariable);
}
public static void TestI(StringWrapper testParameter)
{
testParameter = new StringWrapper("after passing");
// this will change the object that testParameter is pointing/referring
// to but it doesn''t change testVariable unless you use a reference
// parameter as indicated in other answers
}
En realidad, habría sido lo mismo para cualquier objeto, es decir, ser un tipo de referencia y pasar por referencia son 2 cosas diferentes en c #.
Esto funcionaría, pero eso se aplica independientemente del tipo:
public static void TestI(ref string test)
También sobre la cadena siendo un tipo de referencia, también es especial. Está diseñado para ser inmutable, por lo que todos sus métodos no modificarán la instancia (devuelven una nueva). También tiene algunas cosas adicionales para el rendimiento.
Esta es una buena forma de pensar sobre la diferencia entre los tipos de valores, pasar por valor, tipos de referencia y pasar por referencia:
Una variable es un contenedor.
Una variable de tipo valor contiene una instancia. Una variable de tipo de referencia contiene un puntero a una instancia almacenada en otro lugar.
La modificación de una variable de tipo valor muta la instancia que contiene. La modificación de una variable de tipo de referencia muta la instancia a la que apunta.
Las variables de tipo de referencia separadas pueden apuntar a la misma instancia. Por lo tanto, la misma instancia puede mutarse a través de cualquier variable que lo señale.
Un argumento pasado por valor es un contenedor nuevo con una nueva copia del contenido. Un argumento pasado por referencia es el contenedor original con su contenido original.
Cuando un argumento de tipo valor se pasa por valor: la reasignación del contenido del argumento no tiene ningún efecto fuera del alcance, porque el contenedor es único. La modificación del argumento no tiene ningún efecto fuera del alcance, porque la instancia es una copia independiente.
Cuando un argumento de tipo referencia es pasado por valor: la reasignación del contenido del argumento no tiene ningún efecto fuera del alcance, porque el contenedor es único. La modificación del contenido del argumento afecta el alcance externo, porque el puntero copiado apunta a una instancia compartida.
Cuando un argumento se pasa por referencia: la reasignación del contenido del argumento afecta el alcance externo, porque el contenedor se comparte. La modificación del contenido del argumento afecta el alcance externo, porque el contenido se comparte.
En conclusión:
Una variable de cadena es una variable de tipo de referencia. Por lo tanto, contiene un puntero a una instancia almacenada en otro lugar. Cuando pasa por valor, su puntero se copia, por lo que modificar un argumento de cadena debería afectar a la instancia compartida. Sin embargo, una instancia de cadena no tiene propiedades mutables, por lo que un argumento de cadena no se puede modificar de todos modos. Cuando se pasa por referencia, el contenedor del puntero se comparte, por lo que la reasignación seguirá afectando al alcance externo.
La referencia a la cadena se pasa por valor. Hay una gran diferencia entre pasar una referencia por valor y pasar un objeto por referencia. Es desafortunado que la palabra "referencia" se use en ambos casos.
Si pasa la referencia de cadena por referencia, funcionará como espera:
using System;
class Test
{
public static void Main()
{
string test = "before passing";
Console.WriteLine(test);
TestI(ref test);
Console.WriteLine(test);
}
public static void TestI(ref string test)
{
test = "after passing";
}
}
Ahora necesita distinguir entre realizar cambios en el objeto al que hace referencia una referencia y hacer un cambio en una variable (como un parámetro) para permitir que se refiera a un objeto diferente. No podemos realizar cambios en una cadena porque las cadenas son inmutables, pero podemos demostrarlo con un StringBuilder
:
using System;
using System.Text;
class Test
{
public static void Main()
{
StringBuilder test = new StringBuilder();
Console.WriteLine(test);
TestI(test);
Console.WriteLine(test);
}
public static void TestI(StringBuilder test)
{
// Note that we''re not changing the value
// of the "test" parameter - we''re changing
// the data in the object it''s referring to
test.Append("changing");
}
}
Vea mi artículo sobre el paso de parámetros para más detalles.
Las respuestas anteriores son útiles, me gustaría agregar un ejemplo que creo que demuestra claramente lo que sucede cuando pasamos el parámetro sin la palabra clave ref, incluso cuando ese parámetro es un tipo de referencia:
MyClass c = new MyClass(); c.MyProperty = "foo";
CNull(c); // only a copy of the reference is sent
Console.WriteLine(c.MyProperty); // still foo, we only made the copy null
CPropertyChange(c);
Console.WriteLine(c.MyProperty); // bar
private void CNull(MyClass c2)
{
c2 = null;
}
private void CPropertyChange(MyClass c2)
{
c2.MyProperty = "bar"; // c2 is a copy, but it refers to the same object that c does (on heap) and modified property would appear on c.MyProperty as well.
}
Si tenemos que responder a la pregunta: String es un tipo de referencia y se comporta como una referencia. Pasamos un parámetro que contiene una referencia a, no la cadena real. El problema está en la función:
public static void TestI(string test)
{
test = "after passing";
}
La test
parámetro contiene una referencia a la cadena, pero es una copia. Tenemos dos variables que apuntan a la cadena. Y como cualquier operación con cadenas realmente crea un nuevo objeto, hacemos que nuestra copia local apunte a la nueva cadena. Pero la variable de test
original no se cambia.
Las soluciones sugeridas para poner ref
en la declaración de función y en el trabajo de invocación, porque no pasaremos el valor de la variable de test
, sino que pasaremos solo una referencia al mismo. Por lo tanto, cualquier cambio dentro de la función reflejará la variable original.
Quiero repetir al final: String es un tipo de referencia pero dado que es inmutable, la test = "after passing";
línea test = "after passing";
en realidad crea un nuevo objeto y se cambia la copia de la test
variable para apuntar a la nueva cadena.
Tratar:
public static void TestI(ref string test)
{
test = "after passing";
}