Por que usar a palavra-chave 'ref' ao passar um objeto?

290

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

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 é "Bar", o que significa que o objeto foi passado como referência.

Ryan
fonte

Respostas:

298

Passe a refse você deseja alterar qual é o objeto:

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, tnão se refere ao original new TestRef, mas se refere a um objeto completamente diferente.

Isso também pode ser útil se você quiser alterar o valor de um objeto imutável, por exemplo, a string. Você não pode alterar o valor de uma stringdepois que ele foi criado. Mas, usando a ref, você pode criar uma função que altera a string para outra que tenha um valor diferente.

Edit: Como outras pessoas mencionaram. Não é uma boa ideia usar a refmenos que seja necessário. O uso refdá 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 manejem essa possibilidade.

Além disso, quando o tipo de parâmetro é um objeto, as variáveis ​​de objeto sempre atuam como referências ao objeto. Isso significa que, quando a refpalavra-chave é 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), se esse parâmetro for atribuído ao método, o valor do argumento que foi passado será alterado após o retorno do método:

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;
}
Scott Langham
fonte
88

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 com cuidado cada vez que isso aparecer em grupos de notícias :)

Jon Skeet
fonte
1
Bem, eu encontrei o problema ao atualizar o VB6 para o código .Net C #. Existem assinaturas de função / método que usam parâmetros ref, out e plain. Então, como podemos distinguir melhor a diferença entre um parâmetro simples e um ref?
BonCodigo 18/03/2015
2
@bonCodigo: Não sabe ao certo o que você quer dizer com "distinguir melhor" - faz parte da assinatura, e você precisa especificar também refno site da chamada ... onde mais você gostaria que fosse distinguido? A semântica também é razoavelmente clara, mas precisa ser expressa com cuidado (em vez de "objetos são passados ​​por referência", que é a simplificação excessiva comum).
Jon Skeet
Eu não sei porque o Visual Studio ainda não mostra explicitamente o que é passado
MonsterMMORPG
3
@MonsterMMORPG: Eu não sei o que você quer dizer com isso, eu tenho medo.
Jon Skeet
56

No .NET, quando você passa qualquer parâmetro para um método, uma cópia é criada. Nos 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, também é feita uma cópia, mas é uma cópia de uma referência, ou seja, agora você tem DUAS referências na memória para o mesmo objeto. Portanto, se você usar a referência para modificar o objeto, ele será modificado. Mas se você modificar a própria referência - devemos lembrar que é uma cópia -, quaisquer alterações também serão perdidas ao sair do método.

Como as pessoas disseram anteriormente, 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 métodos acima não modificam o objeto 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; }
        }
    }
Ricardo Amores
fonte
6
Eu gosto mais desta resposta do que da resposta aceita. Ele explica mais claramente o que está acontecendo ao passar uma variável de tipo de referência com a palavra-chave ref. Obrigado!
Stefan
17

Como TestRef é uma classe (que são objetos de referência), você pode alterar o conteúdo dentro de t sem passar como ref. No entanto, se você passar t como ref, TestRef poderá alterar a que t original se refere. ou seja, faça apontar para um objeto diferente.

Ferruccio
fonte
16

Com refvocê pode escrever:

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

E t será alterado após a conclusão do método.

Rinat Abdullin
fonte
8

Pense nas variáveis ​​(por exemplo foo) dos tipos de referência (por exemplo List<T>) como contendo identificadores de objeto no formato "Objeto # 24601". Suponha que a instrução foo = new List<int> {1,5,7,9};faça foocom que o objeto "Objeto # 24601" (uma lista com quatro itens). A chamada foo.Lengthpedirá o comprimento do objeto nº 24601 e responderá 4, sendo foo.Lengthigual a 4.

Se foofor passado para um método sem usar ref, esse método poderá fazer alterações no Objeto # 24601. Como conseqüência de tais alterações, foo.Lengthpode não ser mais igual a 4. O próprio método, no entanto, não poderá ser alterado foo, o que continuará mantendo o "Objeto # 24601".

Passar foocomo refparâmetro permitirá que o método chamado faça alterações não apenas no Objeto # 24601, mas também em foosi mesmo. O método pode criar um novo Objeto # 8675309 e armazenar uma referência a isso em foo. Se isso acontecer, foonão conteria mais "Objeto nº 24601", mas sim "Objeto # 8675309".

Na prática, as variáveis ​​do tipo de referência não contêm seqüências de caracteres do formulário "Object # 8675309"; eles nem guardam nada que possa ser convertido significativamente em um número. Embora cada variável do tipo de referência mantenha algum padrão de bits, não há relacionamento fixo entre os padrões de bits armazenados nessas variáveis ​​e os objetos que eles identificam. Não há como o código extrair informações de um objeto ou uma referência a ele e, posteriormente, determinar se outra referência identificou o mesmo objeto, a menos que o código contenha ou soubesse de uma referência que identificasse o objeto original.

supercat
fonte
5

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

pezi_pink_squirrel
fonte
3

Usando a refpalavra-chave com tipos de referência, você está efetivamente passando uma referência para a referência. De muitas maneiras, é o mesmo que usar a outpalavra - chave, mas com a pequena diferença de que não há garantia de que o método realmente atribua algo ao refparâmetro 'ed.

Isak Savo
fonte
3

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

  • Chamador
  • Callee.
guneysus
fonte
1

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 número inteiro para um método, por exemplo, e faça com que o método modifique o número inteiro em seu nome.

Andrew
fonte
4
Quer você esteja passando uma referência ou um valor de tipo de valor, o comportamento padrão é passar por valor. Você só precisa entender que, com tipos de referência, o valor que você está passando é uma referência. Isso não é o mesmo que passar por referência.
Jon Skeet
1

Ref indica se a função pode colocar as mãos no próprio objeto ou apenas em seu valor.

Passar por referência não está vinculado a um idioma; é uma estratégia de ligação de parâmetro ao lado de passar por valor, passar por nome, passar por necessidade etc ...

Uma nota lateral: o nome da classe TestRefé uma escolha horrivelmente ruim nesse contexto;).

xtofl
fonte