Por que usar a palavra-chave ‘ref’ ao passar um object?

Se estou passando um object para um método, por que devo usar a palavra-chave ref? Este não é o comportamento padrão?

Por exemplo:

class Program { static void Main(string[] args) { TestRef t = new TestRef(); t.Something = "Foo"; DoSomething(t); Console.WriteLine(t.Something); } static public void DoSomething(TestRef t) { t.Something = "Bar"; } } public class TestRef { public string Something { get; set; } } 

A saída é “Barra”, o que significa que o object foi passado como referência.

Passe um ref se você quiser mudar o que o object é:

 TestRef t = new TestRef(); t.Something = "Foo"; DoSomething(ref t); void DoSomething(ref TestRef t) { t = new TestRef(); t.Something = "Not just a changed t, but a completely different TestRef object"; } 

Depois de chamar DoSomething, t não se refere ao new TestRef original, mas refere-se a um object completamente diferente.

Isso pode ser útil também se você quiser alterar o valor de um object imutável, por exemplo, uma string . Você não pode alterar o valor de uma string depois de criada. Mas, usando um ref , você pode criar uma function que altere a string para outra que tenha um valor diferente.

Edit: Como outras pessoas mencionaram. Não é uma boa ideia usar o ref menos que seja necessário. Usar ref fornece ao método a liberdade de alterar o argumento para outra coisa, os chamadores do método precisarão ser codificados para garantir que eles lidem com essa possibilidade.

Além disso, quando o tipo de parâmetro é um object, as variables ​​de object sempre atuam como referências ao object. Isso significa que quando a palavra-chave ref é usada, você tem uma referência a uma referência. Isso permite que você faça as coisas conforme descrito no exemplo acima. Mas, quando o tipo de parâmetro é um valor primitivo (por exemplo, int ), então se este parâmetro é atribuído dentro do método, o valor do argumento que foi passado será alterado após o método retornar:

 int x = 1; Change(ref x); Debug.Assert(x == 5); WillNotChange(x); Debug.Assert(x == 5); // Note: x doesn't become 10 void Change(ref int x) { x = 5; } void WillNotChange(int x) { x = 10; } 

Você precisa distinguir entre “passar uma referência por valor” e “passar um parâmetro / argumento por referência”.

Eu escrevi um artigo razoavelmente longo sobre o assunto para evitar ter que escrever cuidadosamente cada vez que isso acontece em grupos de notícias 🙂

No .NET quando você passa qualquer parâmetro para um método, uma cópia é criada. Em tipos de valor significa que qualquer modificação feita no valor está no escopo do método e é perdida quando você sai do método.

Ao passar um tipo de referência, uma cópia também é feita, mas é uma cópia de uma referência, ou seja, agora você tem duas referências na memory para o mesmo object. Então, se você usar a referência para modificar o object, ele será modificado. Mas se você modificar a referência em si – devemos lembrar que é uma cópia -, todas as alterações também serão perdidas ao sair do método.

Como as pessoas disseram antes, uma atribuição é uma modificação da referência, portanto, é perdida:

 public void Method1(object obj) { obj = new Object(); } public void Method2(object obj) { obj = _privateObject; } 

Os methods acima não modificam o object original.

Uma pequena modificação do seu exemplo

  using System; class Program { static void Main(string[] args) { TestRef t = new TestRef(); t.Something = "Foo"; DoSomething(t); Console.WriteLine(t.Something); } static public void DoSomething(TestRef t) { t = new TestRef(); t.Something = "Bar"; } } public class TestRef { private string s; public string Something { get {return s;} set { s = value; } } } 

Como TestRef é uma class (que são objects de referência), você pode alterar o conteúdo dentro de t sem passá-lo como um ref. No entanto, se você passar t como um ref, TestRef pode alterar o que o original t se refere. ou seja, apontá-lo para um object diferente.

Com ref você pode escrever:

 static public void DoSomething(ref TestRef t) { t = new TestRef(); } 

E t será alterado depois que o método for concluído.

Pense em variables ​​(por exemplo, foo ) de tipos de referência (por exemplo, List ) como identificadores de object de retenção no formato “Objeto # 24601”. Suponha que a declaração foo = new List {1,5,7,9}; faz com que foo segure “Objeto # 24601” (uma lista com quatro itens). Então, chamando foo.Length perguntará ao Objeto # 24601 o seu tamanho, e ele responderá 4, então foo.Length será igual a 4.

Se foo é passado para um método sem usar ref , esse método pode fazer alterações no object # 24601. Como conseqüência de tais mudanças, foo.Length pode não ser mais igual a 4. O método em si, no entanto, será incapaz de alterar foo , que continuará a conter “Objeto # 24601”.

Passar foo como um parâmetro ref permitirá que o método chamado faça alterações não apenas no Objeto nº 24601, mas também no próprio foo . O método pode criar um novo Objeto nº 8675309 e armazenar uma referência a isso no foo . Se isso acontecer, foo não foo mais “Objeto # 24601”, mas sim “Objeto # 8675309”.

Na prática, as variables ​​de tipo de referência não contêm seqüências de caracteres do formulário “Objeto # 8675309”; eles nem detêm nada que possa ser significativamente convertido em um número. Mesmo que cada variável de tipo de referência mantenha algum padrão de bits, não há relação fixa entre os padrões de bits armazenados em tais variables ​​e os objects que eles identificam. Não há código de maneira que possa extrair informações de um object ou uma referência a ele e, posteriormente, determinar se outra referência identificou o mesmo object, a menos que o código mantenha ou saiba de uma referência que identificou o object original.

Ao usar a palavra-chave ref com tipos de referência, você está efetivamente passando uma referência para a referência. Em muitos aspectos, é o mesmo que usar a palavra out chave out mas com a menor diferença de que não há garantia de que o método realmente atribuirá qualquer coisa ao parâmetro ref ‘ed.

Isso é como passar um ponteiro para um ponteiro em C. Em .NET, isso permitirá que você altere o que o original T se refere, pessoalmente , embora eu pense que, se você estiver fazendo isso no .NET, provavelmente terá um problema de design!

ref imita (ou se comporta) como uma área global apenas para dois escopos:

  • Chamador
  • Cale.

Se você está passando um valor, no entanto, as coisas são diferentes. Você pode forçar um valor a ser passado por referência. Isso permite que você passe um inteiro para um método, por exemplo, e faça o método modificar o inteiro em seu nome.

Ref denota se a function pode obter suas mãos no próprio object ou apenas em seu valor.

Passar por referência não está ligado a um idioma; é uma estratégia de vinculação de parâmetros ao lado de pass-by-value, passar pelo nome, passar pela necessidade etc …

Um sidenote: o nome da class TestRef é uma escolha terrivelmente ruim neste contexto;).