Pasar objetos por referencia o valor en C #

234

En C #, siempre he pensado que las variables no primitivas se pasan por referencia y los valores primitivos se pasan por valor.

Entonces, al pasar a un método cualquier objeto no primitivo, cualquier cosa que se haga al objeto en el método afectaría al objeto que se pasa. (C # 101 cosas)

Sin embargo, he notado que cuando paso un objeto System.Drawing.Image, ¿este no parece ser el caso? Si paso un objeto system.drawing.image a otro método, y cargo una imagen en ese objeto, entonces dejo que ese método salga del alcance y vuelva al método de llamada, ¿esa imagen no se carga en el objeto original?

¿Por qué es esto?

Miguel
fuente
20
Todas las variables se pasan por valor por defecto en C #. Está pasando el valor de la referencia en el caso de los tipos de referencia.
Andrew Barber

Respuestas:

503

Los objetos no se pasan en absoluto. Por defecto, el argumento se evalúa y su valor se pasa, por valor, como el valor inicial del parámetro del método al que está llamando. Ahora, el punto importante es que el valor es una referencia para los tipos de referencia, una forma de llegar a un objeto (o nulo). Los cambios a ese objeto serán visibles desde la persona que llama. Sin embargo, cambiar el valor del parámetro para referirse a un objeto diferente no será visible cuando use el valor de pasar por valor, que es el valor predeterminado para todos los tipos.

Si desea usar paso por referencia, debe usar outo ref, si el tipo de parámetro es un tipo de valor o un tipo de referencia. En ese caso, efectivamente la variable en sí se pasa por referencia, por lo que el parámetro usa la misma ubicación de almacenamiento que el argumento, y el llamante ve los cambios en el parámetro en sí.

Entonces:

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(...);
}

Tengo un artículo que entra en muchos más detalles en esto . Básicamente, "pasar por referencia" no significa lo que crees que significa.

Jon Skeet
fuente
2
¡Tienes razón, no vi eso! Cargué image = Image.FromFile (..) y eso reemplazó la imagen variable y no cambió el objeto. :) por supuesto.
michael
1
@Adeem: No del todo: no hay un "objeto de parámetro", está el objeto al que se refiere el valor del parámetro. Creo que tienes la idea correcta, pero la terminología importa :)
Jon Skeet
2
Si descartamos las palabras clave refy outdesde c #, ¿está bien decir que c # pasa los parámetros de la misma manera que java, es decir, siempre por valor? ¿Hay alguna diferencia con Java?
banda ancha
1
@broadband: Sí, el modo de paso predeterminado es por valor. Aunque, por supuesto, C # tiene punteros y tipos de valores personalizados, lo que lo hace todo un poco más complicado que en Java.
Jon Skeet
3
@Vippy: No, para nada. Es una copia de la referencia . Le sugiero que lea el artículo vinculado.
Jon Skeet
18

Una muestra de código más para mostrar esto:

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";
}

Y la salida:

TestPlain: 0

TestRef: 5

TestObjPlain: prueba

TestObjRef: TestObjRef

vmg
fuente
2
Entonces, básicamente, el tipo de referencia todavía NECESITA PASARSE como referencia si queremos ver los cambios en la función de llamada.
Irrompible el
1
Las cadenas son tipos de referencia inmutables. Inmutable significa que no se puede cambiar después de haber sido creado. Cada cambio en una cadena creará una nueva cadena. Es por eso que las cadenas deben pasarse como 'ref' para obtener un cambio en el método de llamada. Se pueden pasar otros objetos (por ejemplo, empleado) sin 'ref' para recuperar los cambios en el método de llamada.
Himalaya Garg
1
@vmg, según HimalayaGarg, este no es un muy buen ejemplo. Debe incluir otro ejemplo de tipo de referencia que no sea inmutable.
Daniel
11

Se han agregado muchas buenas respuestas. Todavía quiero contribuir, podría aclarar un poco más.

Cuando pasa una instancia como argumento al método, pasa la copyde la instancia. Ahora, si la instancia que pasa es un value type(reside en el stack) pasa la copia de ese valor, por lo que si lo modifica, no se reflejará en la persona que llama. Si la instancia es un tipo de referencia, pasa la copia de la referencia (nuevamente reside en stack) al objeto. Entonces tienes dos referencias al mismo objeto. Ambos pueden modificar el objeto. Pero si dentro del cuerpo del método, crea una instancia de un nuevo objeto, su copia de la referencia ya no se referirá al objeto original, sino al nuevo objeto que acaba de crear. Entonces terminarás teniendo 2 referencias y 2 objetos.

OlegI
fuente
¡Esta debería ser la respuesta elegida!
ENE
¡Concuerdo completamente! :)
JOSEFtw
8

Supongo que es más claro cuando lo haces así. Recomiendo descargar LinqPad para probar cosas 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";
}

Y eso debería dar salida

WontUpdate

Nombre: Egli, Apellido: Becerra

Actualizar implícitamente

Nombre: Favio, Apellido: Becerra

Actualizar explícitamente

Nombre: Favio, Apellido: Becerra

Egli Becerra
fuente
y qué pasa con el vacío estático público WhatAbout (Person p) {p = new Person () {FirstName = "First", LastName = "Last"}; }. :)
Marin Popov
4

Cuando pasa el System.Drawing.Imageobjeto type a un método, en realidad está pasando una copia de referencia a ese objeto.

Entonces, si dentro de ese método está cargando una nueva imagen, está cargando usando una referencia nueva / copiada. No estás haciendo cambios en el 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
fuente
-1

In Pass By Reference ¡Solo agrega "ref" en los parámetros de la función y una cosa más debería declarar la función "static" porque main es static (# 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);  
        }
    }
}
usuario5593590
fuente