2010-07-25 15 views
33

Me hicieron esta pregunta en una entrevista: ¿Es la cadena un tipo de referencia o un tipo de valor?¿Por qué no usamos el nuevo operador al inicializar una cadena?

Dije que es un tipo de referencia. Luego me preguntó por qué no utilizamos un nuevo operador al inicializar la cadena. Lo dije porque el lenguaje C# tiene una sintaxis más simple para crear una cadena y el compilador convierte automáticamente el código en una llamada para el constructor de la clase System.String.

¿Es correcta o no esta respuesta?

+4

[Parcialmente correcta] (http://msdn.microsoft.com/en-us/library/system .string_members.aspx), pero las cadenas son un poco más complejas, y se almacenan en caché y se comparten de maneras extrañas. Buena suerte. – Kobi

Respuesta

29

Las cadenas son tipos de referencia inmutables. Está la instrucción ldstr IL que permite empujar una nueva referencia de objeto a un literal de cadena. Así que cuando usted escribe:

string a = "abc"; 

Las pruebas del compilador si el "abc" literal ya se ha definido en los metadatos y si no declararlo. Entonces se traduce este código en la siguiente instrucción IL:

ldstr "abc" 

Lo que básicamente hace que el punto de a variable local a la cadena literal definido en los metadatos.

Así que yo diría que su respuesta no es del todo correcta, ya que el compilador no traduce esto en una llamada a un constructor.

4

No. El compilador no cambia la construcción. ¿Qué tipo debe ser el argumento del constructor? ¿Cuerda? ;-)

Los literales de cadena son constantes sin nombre.

Además, puede inicializar cualquier clase con una cadena literal, si es compatible con un operador:

public class UnitTest1 { 
     class MyStringable { 
     public static implicit operator MyStringable(string value) { 
      return new MyStringable(); 
     } 
     } 

     [TestMethod] 
     public void MyTestMethod() { 
     MyStringable foo = "abc"; 
     } 
    } 


Editar Para ser más claro: como lo pidió, si cadena se ser convertido en cualquier llamada de constructor, echemos un vistazo al código IL.

tomado este método de prueba:

[TestClass] 
    class MyClass { 
     [TestMethod] 
     public void MyTest() { 
     string myString = "foo"; 
     if (myString == "bar") 
      Console.WriteLine("w00t"); 
     } 
    } 

Crea el siguiente código IL:

.method public hidebysig instance void MyTest() cil managed 
{ 
    .custom instance void [Microsoft.VisualStudio.QualityTools.UnitTestFramework]Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute::.ctor() 
    .maxstack 2 
    .locals init (
     [0] string myString, 
     [1] bool CS$4$0000) 
    L_0000: nop 
    L_0001: ldstr "foo" 
    L_0006: stloc.0 
    L_0007: ldloc.0 
    L_0008: ldstr "bar" 
    L_000d: call bool [mscorlib]System.String::op_Equality(string, string) 
    L_0012: ldc.i4.0 
    L_0013: ceq 
    L_0015: stloc.1 
    L_0016: ldloc.1 
    L_0017: brtrue.s L_0024 
    L_0019: ldstr "w00t" 
    L_001e: call void [mscorlib]System.Console::WriteLine(string) 
    L_0023: nop 
    L_0024: ret 
} 

Como se puede ver, todos los valores de cadena (foo, bar y w00t) siguen siendo las cadenas y no llaman cualquier constructor oculto.

Espero que esto sea más explicativo.

+5

Eso es increíblemente genial, pero no veo muy bien cómo ayuda explicar las cadenas. – Kobi

+0

La primera parte intentó ser la explicación. Las cadenas son una función de lenguaje C# integrada. Los literales de cadena son cadenas y el compilador nunca los cambiará en nada proporcionado a un constructor de cadenas. Tal vez la respuesta es demasiado descuidada ... ¡Lo siento! –

+0

Cambié mi respuesta para ser más claro. –

11

Bueno, es correcto que el compilador tenga una sintaxis especial que simplifique la creación de cadenas.

La parte sobre el compilador que produce una llamada al constructor no es realmente correcta. Los literales de cadena se crean cuando se inicia la aplicación, de modo que cuando se utiliza el literal de cadena, solo se trata de una asignación de una referencia a un objeto ya existente.

Si asigna una cadena literal en un bucle:

string[] items = new string[10]; 
for (int i = 0; i < 10; i++) { 
    items[i] = "test"; 
} 

no va a crear un nuevo objeto de cadena para cada iteración, se acaba de copiar la misma referencia en cada elemento.

Otras dos cosas dignas de mención sobre los literales de cadenas es que el compilador no crea duplicados y los combina automáticamente si los concatena. Si se utiliza la misma cadena literal más de una vez, se utilizará el mismo objeto:

string a = "test"; 
string b = "test"; 
string c = "te" + "st"; 

Las variables a, bc y todos apuntan al mismo objeto.

la clase String tiene también constructores que se pueden utilizar:

string[] items = new string[10]; 
for (int i = 0; i < 10; i++) { 
    items[i] = new String('*', 42); 
} 

En este caso, en realidad se obtendrá diez objetos de cadena separados.

0

embargo, podemos utilizar las nuevas mientras que el operador para inicializar la cadena

String str = new char[] {'s','t','r'}; 

Es esta respuesta correcta o no?

No, la cadena se almacena en caché y se usa, digamos como en el IL.

28

No es exactamente la respuesta correcta. Strings son "especiales" tipos de referencia. Ellos son inmutables. Tiene razón en que el compilador hace algo internamente, pero no es la llamada de constructor. Llama al ldstr que empuja una nueva referencia de objeto a un literal de cadena almacenado en los metadatos.

muestra C# código:

class Program 
{ 
    static void Main() 
    { 
     string str; 
     string initStr = "test"; 
    } 
} 

y aquí está el código IL

.method private hidebysig static void Main() cil managed 
{ 
    .entrypoint 
    // Code size  8 (0x8) 
    .maxstack 1 
    .locals init ([0] string str, 
      [1] string initStr) 
    IL_0000: nop 
    IL_0001: ldstr  "test" 
    IL_0006: stloc.1 
    IL_0007: ret 
} // end of method Program::Main 

Se puede ver ldstr llamada anteriormente.

Aún más debido a la inmutabilidad de las cadenas, es posible mantener cadenas únicas/únicas. Todas las cadenas se mantienen en la tabla hash donde la clave es el valor de la cadena y el valor es la referencia a esa cadena. Cada vez que tenemos una nueva cadena, CLR comprueba que ya hay una cadena de este tipo en la tabla hash. Si hay , entonces no se asigna memoria nueva y la referencia se establece en esta cadena existente.

se puede ejecutar este código para comprobar:

class Program 
{ 
    static void Main() 
    { 
     string someString = "abc"; 
     string otherString = "efg"; 

     // will retun false 
     Console.WriteLine(Object.ReferenceEquals(someString, otherString)); 

     someString = "efg"; 

     // will return true 
     Console.WriteLine(Object.ReferenceEquals(someString, otherString)); 
    } 
}  
+1

¡Genial! Gracias por la explicación detallada, especialmente sobre la tabla hash interna. Nunca pensé en eso. – NDeveloper

+0

Todas las cadenas no se mantienen en una tabla hash, es decir, solo cadenas intercaladas. Los literales de cadena están internados, pero cualquier cadena nueva que se cree no se interna automáticamente. – Guffa

+0

gran explicación. Me preguntaba qué sucederá cuando asignamos un nuevo valor a la variable de cadena. Desde que descubrí que el hilo es una clase. – ExpertLoser

0

Esta es mi opinión, no estoy del todo seguro, así que tome mi respuesta con un grano de sal.

Los literales de cadena en .NET son independientes, su longitud u otra estructura de datos se incluye internamente en el valor literal. Entonces, a diferencia de C, asignar literal de cadena en .NET es simplemente una cuestión de asignar la dirección de memoria de la estructura de datos completa de la cadena.En C, necesitamos usar new en la clase de cadena, ya que necesita asignar otras estructuras de datos alrededor de una cadena terminada en nulo, por ejemplo, la longitud.

1

Como todos dijeron, la cadena es inmutable, por lo que no hay una llamada de constructor implícita. Me gustaría añadir la siguiente referencia para usted, que puede limpiar el aire un poco más:

String Immutability

Cuestiones relacionadas