Passando objetos por referência ou valor em c #

234

Em C #, sempre pensei que variáveis ​​não primitivas fossem passadas por referência e valores primitivos passados ​​por valor.

Portanto, ao passar para um método qualquer objeto não primitivo, qualquer coisa feita ao objeto no método afetará o objeto que está sendo passado. (Material C # 101)

No entanto, notei que, quando passo um objeto System.Drawing.Image, isso não parece ser o caso? Se eu passar um objeto system.drawing.image para outro método e carregar uma imagem nesse objeto, deixe esse método sair do escopo e voltar ao método de chamada, essa imagem não será carregada no objeto original?

Por que é isso?

Michael
fonte
20
Todas as variáveis ​​são passadas por valor por padrão em C #. Você está passando o valor da referência no caso de tipos de referência.
Andrew Barber

Respostas:

503

Objetos não são passados. Por padrão, o argumento é avaliado e seu valor é passado, por valor, como o valor inicial do parâmetro do método que você está chamando. Agora, o ponto importante é que o valor é uma referência para tipos de referência - uma maneira de chegar a um objeto (ou nulo). As alterações nesse objeto serão visíveis no chamador. No entanto, alterar o valor do parâmetro para se referir a um objeto diferente não será visível quando você estiver usando a passagem por valor, que é o padrão para todos os tipos.

Se você deseja usar passagem por referência, deve usar outou ref, se o tipo de parâmetro é um tipo de valor ou um tipo de referência. Nesse caso, efetivamente a variável em si é passada por referência; portanto, o parâmetro usa o mesmo local de armazenamento que o argumento - e as alterações no próprio parâmetro são vistas pelo chamador.

Assim:

public void Foo(Image image)
{
    // This change won't be seen by the caller: it's changing the value
    // of the parameter.
    image = Image.FromStream(...);
}

public void Foo(ref Image image)
{
    // This change *will* be seen by the caller: it's changing the value
    // of the parameter, but we're using pass by reference
    image = Image.FromStream(...);
}

public void Foo(Image image)
{
    // This change *will* be seen by the caller: it's changing the data
    // within the object that the parameter value refers to.
    image.RotateFlip(...);
}

Eu tenho um artigo que entra em muito mais detalhes nisso . Basicamente, "passar por referência" não significa o que você pensa que significa.

Jon Skeet
fonte
2
Você está certo, eu não vi isso! Eu carregando image = Image.FromFile (..) e que estava substituindo a imagem variável e não alterando o objeto! :) claro.
michael
1
@ Adeem: Não é bem assim - não há "objeto de parâmetro", existe o objeto ao qual o valor do parâmetro se refere. Eu acho que você tem a idéia certa, mas a terminologia importa :)
Jon Skeet
2
Se removermos as palavras ref- chave e outdo c #, não há problema em dizer que o c # passa parâmetros da mesma maneira que o java, ou seja, sempre por valor. Existe alguma diferença com o java.
banda larga
1
@broadband: Sim, o modo de passagem padrão é por valor. Embora, claro, o C # tenha ponteiros e tipos de valor personalizados, o que torna tudo um pouco mais complicado do que em Java.
Jon Skeet
3
@ VIP: Não, de jeito nenhum. É uma cópia da referência . Eu sugiro que você leia o artigo vinculado.
21816 Jon Skeet
18

Mais um exemplo de código para mostrar isso:

void Main()
{


    int k = 0;
    TestPlain(k);
    Console.WriteLine("TestPlain:" + k);

    TestRef(ref k);
    Console.WriteLine("TestRef:" + k);

    string t = "test";

    TestObjPlain(t);
    Console.WriteLine("TestObjPlain:" +t);

    TestObjRef(ref t);
    Console.WriteLine("TestObjRef:" + t);
}

public static void TestPlain(int i)
{
    i = 5;
}

public static void TestRef(ref int i)
{
    i = 5;
}

public static void TestObjPlain(string s)
{
    s = "TestObjPlain";
}

public static void TestObjRef(ref string s)
{
    s = "TestObjRef";
}

E a saída:

TestPlain: 0

TestRef: 5

TestObjPlain: test

TestObjRef: TestObjRef

vmg
fonte
2
Então, basicamente, o tipo de referência ainda precisa ser passado como referência, se quisermos ver as alterações na função de chamada.
Inquebrável
1
Strings são tipos de referência imutáveis. Imutável significa que não pode ser alterado após ter sido criado. Toda mudança em uma string criará uma nova string. É por isso que as strings precisam ser passadas como 'ref' para obter alterações no método de chamada. Outros objetos (por exemplo, funcionário) podem ser passados ​​sem 'ref' para recuperar as alterações no método de chamada.
Himalaya Garg
1
@vmg, conforme HimalayaGarg, este não é um exemplo muito bom. Você precisa incluir outro exemplo de tipo de referência que não seja imutável.
Daniel
11

Muitas boas respostas foram adicionadas. Eu ainda quero contribuir, pode ser que esclareça um pouco mais.

Quando você passa uma instância como argumento para o método, ela passa a copyda instância. Agora, se a instância que você passa é a value type(reside em stack), você passa a cópia desse valor; portanto, se você o modificar, ele não será refletido no chamador. Se a instância for um tipo de referência, você passa a cópia da referência (novamente reside em stack) para o objeto. Então você tem duas referências ao mesmo objeto. Ambos podem modificar o objeto. Mas se, no corpo do método, você instancia um novo objeto, sua cópia da referência não se refere mais ao objeto original, mas ao novo objeto que você acabou de criar. Então você terá 2 referências e 2 objetos.

OlegI
fonte
Essa deve ser a resposta escolhida!
JAN
Eu concordo completamente! :)
JOSEFtw 22/01
8

Eu acho que é mais claro quando você faz assim. Eu recomendo baixar o LinqPad para testar coisas como esta.

void Main()
{
    var Person = new Person(){FirstName = "Egli", LastName = "Becerra"};

    //Will update egli
    WontUpdate(Person);
    Console.WriteLine("WontUpdate");
    Console.WriteLine($"First name: {Person.FirstName}, Last name: {Person.LastName}\n");

    UpdateImplicitly(Person);
    Console.WriteLine("UpdateImplicitly");
    Console.WriteLine($"First name: {Person.FirstName}, Last name: {Person.LastName}\n");

    UpdateExplicitly(ref Person);
    Console.WriteLine("UpdateExplicitly");
    Console.WriteLine($"First name: {Person.FirstName}, Last name: {Person.LastName}\n");
}

//Class to test
public class Person{
    public string FirstName {get; set;}
    public string LastName {get; set;}

    public string printName(){
        return $"First name: {FirstName} Last name:{LastName}";
    }
}

public static void WontUpdate(Person p)
{
    //New instance does jack...
    var newP = new Person(){FirstName = p.FirstName, LastName = p.LastName};
    newP.FirstName = "Favio";
    newP.LastName = "Becerra";
}

public static void UpdateImplicitly(Person p)
{
    //Passing by reference implicitly
    p.FirstName = "Favio";
    p.LastName = "Becerra";
}

public static void UpdateExplicitly(ref Person p)
{
    //Again passing by reference explicitly (reduntant)
    p.FirstName = "Favio";
    p.LastName = "Becerra";
}

E isso deve gerar

WontUpdate

Nome: Egli, Sobrenome: Becerra

UpdateImplicitly

Nome: Favio, Sobrenome: Becerra

UpdateExplicitly

Nome: Favio, Sobrenome: Becerra

Egli Becerra
fonte
e o que dizer sobre estático público WhatAbout (Person p) {p = new Person () {FirstName = "First", LastName = "Last"}; } :)
Marin Popov
4

Quando você passa o System.Drawing.Imageobjeto de tipo para um método, na verdade está passando uma cópia da referência a esse objeto.

Portanto, se dentro desse método você estiver carregando uma nova imagem, você estará carregando usando uma referência nova / copiada. Você não está fazendo alterações no original.

YourMethod(System.Drawing.Image image)
{
    //now this image is a new reference
    //if you load a new image 
    image = new Image()..
    //you are not changing the original reference you are just changing the copy of original reference
}
Haris Hasan
fonte
-1

Na passagem por referência Você apenas adiciona "ref" nos parâmetros da função e mais uma coisa que deve declarar a função "estática" porque main é estático (# public void main(String[] args))!

namespace preparation
{
  public  class Program
    {
      public static void swap(ref int lhs,ref int rhs)
      {
          int temp = lhs;
          lhs = rhs;
          rhs = temp;
      }
          static void Main(string[] args)
        {
            int a = 10;
            int b = 80;

  Console.WriteLine("a is before sort " + a);
            Console.WriteLine("b is before sort " + b);
            swap(ref a, ref b);
            Console.WriteLine("");
            Console.WriteLine("a is after sort " + a);
            Console.WriteLine("b is after sort " + b);  
        }
    }
}
user5593590
fonte