Qual é o uso de "ref" para variáveis ​​de tipo de referência em C #?

176

Entendo que, se eu passar um tipo de valor ( int, structetc.) como parâmetro (sem a refpalavra - chave), uma cópia dessa variável é passada para o método, mas se eu usar a refpalavra-chave, uma referência a essa variável será passada, não é novo.

Mas com tipos de referência, como classes, mesmo sem a refpalavra - chave, uma referência é passada para o método, não uma cópia. Então, qual é o uso da refpalavra-chave com tipos de referência?


Considere por exemplo:

var x = new Foo();

Qual é a diferença entre os seguintes?

void Bar(Foo y) {
    y.Name = "2";
}

e

void Bar(ref Foo y) {
    y.Name = "2";
}
Andreas Grech
fonte

Respostas:

154

Você pode alterar quais foopontos usar y:

Foo foo = new Foo("1");

void Bar(ref Foo y)
{
    y = new Foo("2");
}

Bar(ref foo);
// foo.Name == "2"
user7116
fonte
17
assim que você basicamente obter uma referência para a referência original
lhahne
2
Você pode alterar a que a referência original 'se refere', então sim.
user7116
1
Chris, sua explicação é ótima; Obrigado por me ajudar a entender esse conceito.
Andreas Grech
4
Então, usar 'ref' em um objeto é como usar ponteiros duplos em C ++?
Tom Hazel
1
@TomHazel: ish , desde que você está usando ponteiros "duplo" em C ++ para mudar o que o ponteiro aponta.
user7116
29

Há casos em que você deseja modificar a referência real e não o objeto apontado:

void Swap<T>(ref T x, ref T y) {
    T t = x;
    x = y;
    y = t;
}

var test = new[] { "0", "1" };
Swap(ref test[0], ref test[1]);
Mehrdad Afshari
fonte
21

Jon Skeet escreveu um ótimo artigo sobre a passagem de parâmetros em C #. Ele detalha claramente o comportamento exato e o uso de passar parâmetros por valor, por referência ( ref) e por saída ( out).

Aqui está uma citação importante dessa página em relação aos refparâmetros:

Os parâmetros de referência não passam os valores das variáveis ​​usadas na chamada do membro da função - eles usam as próprias variáveis. Em vez de criar um novo local de armazenamento para a variável na declaração do membro da função, o mesmo local de armazenamento é usado, portanto, o valor da variável no membro da função e o valor do parâmetro de referência sempre serão os mesmos. Os parâmetros de referência precisam do modificador ref como parte da declaração e da invocação - isso significa que é sempre claro quando você passa algo por referência.

Noldorin
fonte
11
I como a analogia de passar o seu cães coleira para um amigo para passar uma referência por valor ... se decompõe rapidamente, porém, porque eu acho que você faria provavelmente aviso se seu amigo negociadas-up seu shitzu a um doberman antes que ele entregou de volta a coleira ;-)
corlettk
16

Muito bem explicado aqui: http://msdn.microsoft.com/en-us/library/s6938f28.aspx

Resumo do artigo:

Uma variável de um tipo de referência não contém seus dados diretamente; contém uma referência aos seus dados. Quando você passa um parâmetro do tipo de referência por valor, é possível alterar os dados apontados pela referência, como o valor de um membro da classe. No entanto, você não pode alterar o valor da própria referência; ou seja, você não pode usar a mesma referência para alocar memória para uma nova classe e persistir fora do bloco. Para fazer isso, passe o parâmetro usando a palavra-chave ref ou out.

himanshupareek66
fonte
4
A explicação é realmente muito boa. No entanto, as respostas somente de link são desencorajadas no SO. Adicionei um resumo do artigo, como uma conveniência para os leitores aqui.
Marcel
10

Quando você passa um tipo de referência com a palavra-chave ref, passa a referência por referência, e o método que você chama pode atribuir um novo valor ao parâmetro. Essa alteração será propagada para o escopo da chamada. Sem ref, a referência é passada por valor, e isso não acontece.

O C # também possui a palavra-chave 'out', que é muito parecida com ref, exceto que, com 'ref', os argumentos devem ser inicializados antes de chamar o método e, com 'out', você deve atribuir um valor no método de recebimento.

Rytmis
fonte
5

Permite modificar a referência transmitida.

void Bar()
{
    var y = new Foo();
    Baz(ref y);
}

void Baz(ref Foo y)
{
    y.Name = "2";

    // Overwrite the reference
    y = new Foo();
}

Você também pode usar fora se você não se preocupam com a referência passou em:

void Bar()
{
    var y = new Foo();
    Baz(out y);
}

void Baz(out Foo y)
{
    // Return a new reference
    y = new Foo();
}
Hans Van Slooten
fonte
4

Outro monte de código

class O
{
    public int prop = 0;
}

class Program
{
    static void Main(string[] args)
    {
        O o1 = new O();
        o1.prop = 1;

        O o2 = new O();
        o2.prop = 2;

        o1modifier(o1);
        o2modifier(ref o2);

        Console.WriteLine("1 : " + o1.prop.ToString());
        Console.WriteLine("2 : " + o2.prop.ToString());
        Console.ReadLine();
    }

    static void o1modifier(O o)
    {
        o = new O();
        o.prop = 3;
    }

    static void o2modifier(ref O o)
    {
        o = new O();
        o.prop = 4;
    }
}
Svend
fonte
3

Além das respostas existentes:

Como você pediu a diferença dos 2 métodos: Não há variação co (ntra) ao usar refou out:

class Foo { }
class FooBar : Foo { }

static void Bar(Foo foo) { }
static void Bar(ref Foo foo) { foo = new Foo(); }

void Main()
{
    Foo foo = null;
    Bar(foo);           // OK
    Bar(ref foo);       // OK

    FooBar fooBar = null;
    Bar(fooBar);        // OK (covariance)
    Bar(ref fooBar);    // compile time error
}
springy76
fonte
1

Um parâmetro em um método parece estar sempre passando uma cópia, a questão é uma cópia do quê. Uma cópia é feita por um construtor de cópia para um objeto e, como todas as variáveis ​​são Object em C #, acredito que esse seja o caso de todas elas. Variáveis ​​(objetos) são como pessoas que moram em alguns endereços. Mudamos as pessoas que moram nesses endereços ou podemos criar mais referências às pessoas que moram nesses endereços na lista telefônica (faça cópias rasas). Portanto, mais de um identificador pode se referir ao mesmo endereço. Os tipos de referência desejam mais espaço; portanto, diferentemente dos tipos de valor conectados diretamente por uma seta ao seu identificador na pilha, eles têm valor para outro endereço na pilha (um espaço maior para permanecer). Esse espaço precisa ser retirado da pilha.

Tipo de valor: Identificador (contém valor = endereço do valor da pilha) ----> Valor do tipo de valor

Tipo de referência: Identificador (contém valor = endereço do valor da pilha) ----> (contém valor = endereço do valor da pilha) ----> Valor da pilha (na maioria das vezes contém endereços para outros valores), imagine mais setas coladas em diferentes direções para Matriz [0], Matriz [1], matriz [2]

A única maneira de alterar um valor é seguir as setas. Se uma seta for perdida / alterada da maneira que o valor estiver inacessível.

Petar Drianov
fonte
-1

Variáveis ​​de referência transportam o endereço de um lugar para outro, portanto, qualquer atualização nelas em qualquer lugar refletirá em todos os lugares ENTÃO, qual é o uso de REF. A variável de referência (405) é boa até que nenhuma nova memória seja alocada para a variável de referência passada no método.

Depois que a nova memória alocar (410), a alteração de valor neste objeto (408) não será refletida em todos os lugares. Para esta referência vem. Ref é referência de referência, portanto, sempre que a nova memória alocar, ela será conhecida porque está apontando para esse local, portanto, o valor pode ser compartilhado por everyOne. Você pode ver a imagem para obter mais clareza.

Ref na Variável de Referência

srbh
fonte