C # string tipo de referência?

Eu sei que “string” em c # é um tipo de referência. Isso está no MSDN. No entanto, este código não funciona como deveria:

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

A saída deve ser “before passing” “after passing”, pois estou passando a string como um parâmetro e sendo um tipo de referência, a segunda instrução de saída deve reconhecer que o texto foi alterado no método TestI. No entanto, eu recebo “antes de passar” “antes de passar” fazendo parecer que é passado por valor não por ref. Eu entendo que as cordas são imutáveis, mas não vejo como isso explicaria o que está acontecendo aqui. o que estou perdendo? Obrigado.

A referência à string é passada por valor. Há uma grande diferença entre passar uma referência por valor e passar um object por referência. É lamentável que a palavra “referência” seja usada em ambos os casos.

Se você passar a referência da string por referência, ela funcionará conforme o esperado:

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

Agora você precisa distinguir entre fazer alterações no object ao qual uma referência se refere e fazer uma alteração em uma variável (como um parâmetro) para permitir que ela se refira a um object diferente. Não podemos fazer alterações em uma string porque as strings são imutáveis, mas podemos demonstrar isso com um 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"); } } 

Veja meu artigo sobre passagem de parâmetros para mais detalhes.

Se tivermos que responder a pergunta: String é um tipo de referência e se comporta como uma referência. Nós passamos um parâmetro que contém uma referência, não a string real. O problema está na function:

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

O test parâmetro contém uma referência à string, mas é uma cópia. Nós temos duas variables ​​apontando para a string. E como quaisquer operações com strings realmente criam um novo object, fazemos com que nossa cópia local aponte para a nova string. Mas a variável de test original não é alterada.

As soluções sugeridas para colocar ref na declaração de function e no trabalho de invocação, porque não passaremos o valor da variável de test , mas passaremos apenas uma referência a ela. Assim, qualquer alteração dentro da function refletirá a variável original.

Eu quero repetir no final: String é um tipo de referência, mas desde a sua imutável a linha de test = "after passing"; na verdade cria um novo object e a cópia do test da variável é alterada para apontar para a nova string.

Como outros afirmaram, o tipo String no .NET é imutável e sua referência é passada por valor.

No código original, assim que esta linha for executada:

 test = "after passing"; 

então o test não está mais se referindo ao object original. Criamos um novo object String e test atribuído para fazer referência a esse object no heap gerenciado.

Eu sinto que muitas pessoas tropeçam aqui, já que não há nenhum construtor formal visível para lembrá-las. Neste caso, está acontecendo nos bastidores, já que o tipo String tem suporte de idioma em como é construído.

Portanto, é por isso que a mudança para test não é visível fora do escopo do método TestI(string) – passamos a referência por valor e agora esse valor foi alterado! Mas, se a referência de String foi passada por referência, quando a referência for alterada, a veremos fora do escopo do método TestI(string) .

A palavra chave ref ou out é necessária neste caso. Sinto que a palavra out chave out pode ser um pouco mais adequada para essa situação específica.

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

Na verdade, teria sido o mesmo para qualquer object para esse assunto, ou seja, sendo um tipo de referência e passando por referência são duas coisas diferentes em c #.

Isso funcionaria, mas isso se aplica independentemente do tipo:

 public static void TestI(ref string test) 

Também sobre string sendo um tipo de referência, é também um tipo especial. É projetado para ser imutável, portanto, todos os seus methods não modificam a instância (eles retornam um novo). Ele também tem algumas coisas extras para desempenho.

Aqui está uma boa maneira de pensar sobre a diferença entre tipos de valor, passagem por valor, tipos de referência e passagem por referência:

Uma variável é um contêiner.

Uma variável do tipo de valor contém uma instância. Uma variável de tipo de referência contém um ponteiro para uma instância armazenada em outro lugar.

Modificar uma variável do tipo de valor altera a instância que ela contém. Modificar uma variável do tipo de referência altera a instância para a qual ela aponta.

Variáveis ​​separadas do tipo de referência podem apontar para a mesma instância. Portanto, a mesma instância pode sofrer mutação por meio de qualquer variável que aponte para ela.

Um argumento passado por valor é um novo contêiner com uma nova cópia do conteúdo. Um argumento passado por referência é o contêiner original com seu conteúdo original.

Quando um argumento de tipo de valor é passado por valor: Reatribuir o conteúdo do argumento não tem efeito fora do escopo, porque o contêiner é exclusivo. Modificar o argumento não tem efeito fora do escopo, porque a instância é uma cópia independente.

Quando um argumento de tipo de referência é passado por valor: Reatribuir o conteúdo do argumento não tem efeito fora do escopo, porque o contêiner é exclusivo. Modificar o conteúdo do argumento afeta o escopo externo, porque o ponteiro copiado aponta para uma instância compartilhada.

Quando qualquer argumento é passado por referência: Reatribuir o conteúdo do argumento afeta o escopo externo, porque o contêiner é compartilhado. Modificar o conteúdo do argumento afeta o escopo externo, porque o conteúdo é compartilhado.

Em conclusão:

Uma variável de string é uma variável de tipo de referência. Portanto, ele contém um ponteiro para uma instância armazenada em outro lugar. Quando passado por valor, seu ponteiro é copiado, portanto, modificar um argumento de string deve afetar a instância compartilhada. No entanto, uma instância de cadeia não tem propriedades mutáveis, portanto, um argumento de cadeia não pode ser modificado de qualquer maneira. Quando passado por referência, o contêiner do ponteiro é compartilhado, portanto, a reconsignação ainda afetará o escopo externo.

As respostas acima são úteis, gostaria apenas de acrescentar um exemplo que, na minha opinião, demonstra claramente o que acontece quando passamos o parâmetro sem a palavra-chave ref, mesmo quando esse parâmetro é um tipo de referência:

 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. } 

Experimentar:

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

Eu acredito que o seu código é análogo ao seguinte, e você não deveria esperar que o valor fosse alterado pela mesma razão que ele não estaria aqui:

  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 } 

Para mentes curiosas e para completar a conversa: Sim, String é um tipo de referência :

 unsafe { string a = "Test"; string b = a; fixed (char* p = a) { p[0] = 'B'; } Console.WriteLine(a); // output: "Best" Console.WriteLine(b); // output: "Best" } 

Mas note que essa mudança só funciona em um bloco inseguro ! porque as strings são imutáveis (do MSDN):

O conteúdo de um object de string não pode ser alterado depois que o object é criado, embora a syntax faça com que pareça que você pode fazer isso. Por exemplo, quando você escreve esse código, o compilador cria, na verdade, um novo object de string para manter a nova seqüência de caracteres, e esse novo object é atribuído a b. A string “h” é então elegível para garbage collection.

 string b = "h"; b += "ello"; 

E tenha em mente que:

Embora a cadeia seja um tipo de referência, os operadores de igualdade ( == e != ) São definidos para comparar os valores de objects de cadeia, não de referências.