Como as strings são passadas no .NET?

Quando eu passar uma string para uma function, um ponteiro para o conteúdo da string é passado, ou a string inteira é passada para a function na pilha como uma struct seria?

Para responder à sua pergunta, considere o seguinte código:

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

Há três coisas que você precisa saber para prever o que acontecerá aqui e entender por que isso acontece.

  1. Strings são tipos de referência em C #. Mas isso é apenas parte da imagem.
  2. Eles também são imutáveis, então sempre que você fizer algo que pareça estar mudando a sequência, você não está. Uma corda completamente nova é criada, a referência é apontada para ela e a antiga é descartada.
  3. Mesmo que as strings sejam tipos de referência, strMain não é passado por referência. É um tipo de referência, mas a referência está sendo passada por valor . Esta é uma distinção complicada, mas é crucial. Toda vez que você passa um parâmetro sem a palavra-chave ref (sem contar out parâmetros), você passou algo por valor.

Mas o que isso significa?

Passando tipos de referência por valor: você já está fazendo isso

Existem dois grupos de tipos de dados em C #: tipos de referência e tipos de valor . Há também duas maneiras de passar parâmetros em C #: por referência e por valor . Estes soam iguais e são facilmente confundidos. Eles não são a mesma coisa!

Se você passar um parâmetro de QUALQUER tipo e não usar a palavra-chave ref , você terá passado por valor. Se você passou por valor, o que você realmente passou foi uma cópia. Mas se o parâmetro era um tipo de referência, então a coisa que você copiou era a referência, não o que estivesse apontando.

Aqui está a primeira linha do nosso método Main :

 string strMain = "main"; 

Na verdade, existem duas coisas que criamos nesta linha: uma string com o valor main armazenado em algum lugar na memory e uma variável de referência chamada strMain apontando para ela.

 DoSomething(strMain); 

Agora passamos essa referência para DoSomething . Nós passamos por valor, então isso significa que fizemos uma cópia. Mas é um tipo de referência, o que significa que copiamos a referência, não a string em si. Agora temos duas referências que apontam para o mesmo valor na memory.

Dentro do callee

Aqui está o topo do método DoSomething :

 void DoSomething(string strLocal) 

Nenhuma palavra-chave ref , como de costume. Então, o strLocal não é o strMain , mas ambos apontam para o mesmo lugar. Se nós “mudar” strLocal , assim …

 strLocal = "local"; 

… não alteramos o valor armazenado, por si só. Nós re-apontamos a referência. Pegamos a referência chamada strLocal e a strLocal para uma nova sequência. O que acontece com o strMain quando fazemos isso? Nada. Ainda está apontando para a corda antiga!

 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" 

Imutabilidade é importante

Vamos mudar o cenário por um segundo. Imagine que não estamos trabalhando com strings, mas com algum tipo de referência mutável, como uma class criada por você.

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

Se você seguir a referência objLocal para o object para o qual ele aponta, você poderá alterar suas propriedades:

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

Ainda há apenas um MutableThing na memory, e tanto a referência copiada quanto a referência original ainda apontam para ela. As propriedades do próprio MutableThing mudaram :

 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, mas …

… cordas são imutáveis! Não há ChangeMe propriedade ChangeMe para definir. Você não pode fazer strLocal[3] = 'H'; como você poderia com uma matriz char de estilo C; você tem que construir uma nova string inteira em seu lugar. A única maneira de alterar strLocal é apontar a referência em outra sequência, e isso significa que nada que você faça para strLocal pode afetar o strMain . O valor é imutável e a referência é uma cópia.

Portanto, mesmo que as strings sejam tipos de referência, passá-las por valor significa que o que ocorrer no evento não afetará a string no chamador. Mas como eles são tipos de referência, você não precisa copiar toda a string na memory quando quiser passá-la.

Mais resources:

  • Aqui está o melhor artigo que eu li sobre a diferença entre tipos de referência e tipos de valor em C # e por que um tipo de referência não é o mesmo que um parâmetro passado pela referência.
  • Como de costume, Eric Lippert também tem vários excelentes posts sobre o assunto .
  • Ele também tem ótimas coisas sobre imutabilidade .

Strings em C # são objects de referência imutáveis. Isso significa que as referências a elas são repassadas (por valor) e, depois que uma cadeia é criada, você não pode modificá-la. Os methods que produzem versões modificadas da string (substrings, versões aparadas, etc.) criam cópias modificadas da string original.

Strings são casos especiais. Cada instância é imutável. Quando você altera o valor de uma string, você está alocando uma nova string na memory.

Portanto, somente a referência é passada para a sua function, mas quando ela é editada, ela se torna uma nova instância e não modifica a instância antiga.