2009-07-08 18 views
137

Sé que "cadena" en C# es un tipo de referencia. Esto está en MSDN. Sin embargo, este código no funciona como debería entonces:C# string reference type?

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 parámetro y que es un tipo de referencia, el la segunda instrucción de salida debería 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.

+0

Ver el artículo mencionado por Jon a continuación. El comportamiento que mencionas puede ser reproducido por punteros C++ también. – Sesh

+0

Muy buena explicación en [MSDN] (http://msdn.microsoft.com/en-us/library/s6938f28.aspx) también. –

+0

Posible duplicado de [En C#, ¿por qué String es un tipo de referencia que se comporta como un tipo de valor?] (Http://stackoverflow.com/questions/636932/in-c-why-is-string-a-reference-type -that-behaves-like-a-value-type) – Liam

Respuesta

185

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 hace pasar la cadena de referencia por de referencia, que funcionará como se 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 tiene que distinguir entre hacer cambios en el objeto que una referencia se refiere a, y haciendo un cambio a una variable (como un parámetro) para que se refiera a un objeto diferente. No podemos hacer cambios a una cadena porque las cadenas son inmutables, pero podemos demostrar que con un StringBuilder lugar:

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"); 
    } 
} 

Ver my article on parameter passing para más detalles.

+2

de acuerdo, solo quiero aclarar que el uso del modificador ref también funciona para tipos que no son de referencia, es decir, ambos son conceptos bastante separados. – eglasius

+2

@Jon Skeet amaba la nota al margen en su artículo. Debería haber 'referenciado' como su respuesta –

0

Probar:


public static void TestI(ref string test) 
    { 
     test = "after passing"; 
    } 
+1

Su respuesta debería contener más que solo el código. También debe contener una explicación de por qué funciona. –

9

En realidad, habría sido el mismo para cualquier objeto para el que la materia es decir, siendo un tipo de referencia y pasando por referencia son 2 cosas diferentes en C#.

esto iba a funcionar, pero que se aplica independientemente del tipo:

public static void TestI(ref string test) 

También sobre la cadena de ser un tipo de referencia, su también una 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.

25

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"; 
}

test El 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 original test 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 test pero 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 desde su inmutable la línea test = "after passing"; en realidad crea un nuevo objeto y copia de la variable test se cambia para que apunte a la nueva cadena.

0

Creo que su código es análoga a la siguiente, y no debería haber esperado que el valor de haber cambiado por la misma razón que no lo harí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 
} 
6

Aquí es una buena manera de pensar en el 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.

Al modificar una variable de tipo valor, se 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 valor-tipo 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 de referencia 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 contenido del argumento afecta al ámbito externo, ya que el puntero copiado apunta a una instancia compartida.

Cuando se pasa cualquier argumento por referencia: La reasignación del contenido del argumento afecta al ámbito 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.

18

Como han dicho otros, el tipo String en .NET es inmutable y su referencia se pasa por valor.

En el código original, tan pronto como se ejecuta esta línea:

test = "after passing"; 

entonces ya no es test en referencia al objeto original. Creamos un nuevo objetoString y le asignamos test para hacer referencia a ese objeto en el montón administrado.

Creo que mucha gente se tropieza aquí porque no hay un constructor formal visible que se lo recuerde. En este caso, está sucediendo entre bastidores ya que el tipo String tiene soporte de idioma en cómo se construye.

Por lo tanto, esta es la razón por el cambio a test no es visible fuera del alcance del método TestI(string) - hemos pasado la referencia por su valor y ahora que el valor ha cambiado! Pero si la referencia String se pasó por referencia, cuando cambie la referencia la veremos fuera del alcance del método TestI(string).

O bien ref o palabra clave son necesarios en este caso. Creo que la palabra clave out podría ser un poco más adecuada para esta situación 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"; 
    } 
} 
2

respuestas anteriores son útiles, sólo quisiera añadir un ejemplo que creo que está demostrando claramente lo que sucede cuando se pasa 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. 
     }