Eu sei que "string" em c # é um tipo de referência. Isso está no MSDN. No entanto, esse 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 "antes de passar" "depois de passar", pois estou passando a string como 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 e não por ref. Entendo que as strings são imutáveis, mas não vejo como isso explicaria o que está acontecendo aqui. o que estou perdendo? Obrigado.
Respostas:
A referência à string é passada por valor. Há uma grande diferença entre passar uma referência por valor e passar um objeto por referência. É lamentável que a palavra "referência" seja usada nos dois casos.
Se você fazer passar a referência seqüência de referência, ele vai funcionar como você espera:
Agora você precisa distinguir entre fazer alterações no objeto 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 objeto diferente. Não podemos fazer alterações em uma string porque elas são imutáveis, mas podemos demonstrá-la com um
StringBuilder
:Veja meu artigo sobre a passagem de parâmetros para mais detalhes.
fonte
referenced
isso como respostaSe tivermos que responder à pergunta: String é um tipo de referência e se comporta como referência. Passamos um parâmetro que contém uma referência, e não a string real. O problema está na função:
O parâmetro
test
mantém uma referência à string, mas é uma cópia. Temos duas variáveis apontando para a string. E como quaisquer operações com cadeias realmente criam um novo objeto, fazemos nossa cópia local para apontar para a nova cadeia. Mas atest
variável original não é alterada.As soluções sugeridas para colocar
ref
na declaração da função e na invocação funcionam porque não passaremos o valor datest
variável, mas passaremos apenas uma referência a ela. Portanto, quaisquer alterações dentro da função refletirão a variável original.Quero repetir no final: String é um tipo de referência, mas como é imutável, a linha
test = "after passing";
realmente cria um novo objeto e nossa cópia da variáveltest
é alterada para apontar para a nova string.fonte
Como outros já declararam, o
String
tipo no .NET é imutável e sua referência é passada por valor.No código original, assim que essa linha for executada:
então
test
não está mais se referindo ao objeto original. Criamos um novoString
objeto e atribuímostest
a referência a esse objeto no heap gerenciado.Eu sinto que muitas pessoas viajam até aqui, pois não há construtor formal visível para lembrá-las. Nesse caso, isso está acontecendo nos bastidores, pois o
String
tipo tem suporte de linguagem na forma como é construído.Portanto, é por isso que a mudança para
test
não é visível fora do escopo doTestI(string)
método - passamos a referência por valor e agora esse valor mudou! Mas se aString
referência foi passada por referência, quando a referência mudou, a veremos fora do escopo doTestI(string)
método.A palavra - chave ref ou out é necessária neste caso. Acho que a
out
palavra - chave pode ser um pouco mais adequada para essa situação específica.fonte
out
precisa ser atribuído dentro do método para a compilação do código.ref
não tem esse requisito. Tambémout
parâmetros são inicializados fora do método - o código nesta resposta é um contra-exemplo.out
parâmetros podem ser inicializados fora do método, mas não precisam. Nesse caso, queremos inicializar oout
parâmetro para demonstrar um ponto sobre a natureza dostring
tipo no .NET.Na verdade, seria o mesmo para qualquer objeto, por exemplo, ser um tipo de referência e passar por referência são duas coisas diferentes em c #.
Isso funcionaria, mas isso se aplica independentemente do tipo:
Também sobre string ser um tipo de referência, também é especial. Ele foi projetado para ser imutável, para que todos os seus métodos não modifiquem a instância (eles retornam um novo). Ele também possui algumas coisas extras para desempenho.
fonte
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 valor contém uma instância. Uma variável do tipo de referência contém um ponteiro para uma instância armazenada em outro local.
A modificação de uma variável do tipo valor muda a instância que ela contém. A modificação de uma variável do tipo de referência altera a instância para a qual ela aponta.
Variáveis de tipo de referência separadas podem apontar para a mesma instância. Portanto, a mesma instância pode ser modificada por 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 do tipo valor é passado por valor: A reatribuição do conteúdo do argumento não tem efeito fora do escopo, porque o contêiner é exclusivo. A modificação do argumento não tem efeito fora do escopo, porque a instância é uma cópia independente.
Quando um argumento do tipo de referência é passado por valor: A reatribuição do conteúdo do argumento não tem efeito fora do escopo, porque o contêiner é exclusivo. A modificação do 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: A reatribuição do conteúdo do argumento afeta o escopo externo, porque o contêiner é compartilhado. A modificação do conteúdo do argumento afeta o escopo externo, porque o conteúdo é compartilhado.
Em conclusão:
Uma variável de sequência é uma variável do tipo de referência. Portanto, ele contém um ponteiro para uma instância armazenada em outro local. Quando passado por valor, seu ponteiro é copiado, portanto, a modificação de um argumento de string deve afetar a instância compartilhada. No entanto, uma instância de string não possui propriedades mutáveis, portanto, um argumento de string não pode ser modificado de qualquer maneira. Quando passado por referência, o contêiner do ponteiro é compartilhado, portanto, a reatribuição ainda afetará o escopo externo.
fonte
" Uma imagem vale mais que mil palavras ".
Eu tenho um exemplo simples aqui, é semelhante ao seu caso.
Isso é o que aconteceu:
s1
e ass2
variáveis referenciam o mesmo"abc"
objeto de string."abc"
objeto string não se modifica (para"def"
), mas um novo"def"
objeto string é criado e, em seguida, fazs1
referência a ele.s2
ainda faz referência ao"abc"
objeto string, então essa é a saída.fonte
As respostas acima são úteis, eu gostaria de adicionar um exemplo que acho que está demonstrando claramente o que acontece quando passamos o parâmetro sem a palavra-chave ref, mesmo quando esse parâmetro é um tipo de referência:
fonte
Para mentes curiosas e para concluir a conversa: Sim, String é um tipo de referência :
Mas observe que essa alteração funciona apenas em um bloco não seguro ! porque Strings são imutáveis (do MSDN):
E lembre-se de que:
fonte
Acredito que seu código é análogo ao seguinte, e você não deveria esperar que o valor fosse alterado pelo mesmo motivo que não seria aqui:
fonte
Experimentar:
fonte