Passaggio di oggetti per riferimento o valore in C #


234

In C #, ho sempre pensato che le variabili non primitive fossero passate per riferimento e i valori primitivi passati per valore.

Quindi quando si passa a un metodo qualsiasi oggetto non primitivo, tutto ciò che viene fatto sull'oggetto nel metodo influenzerebbe l'oggetto che viene passato. (Roba C # 101)

Tuttavia, ho notato che quando passo un oggetto System.Drawing.Image, questo non sembra essere il caso? Se passo un oggetto system.drawing.image a un altro metodo e carico un'immagine su quell'oggetto, quindi lascia che quel metodo esca dall'ambito e torni al metodo chiamante, quell'immagine non viene caricata sull'oggetto originale?

Perchè è questo?


20
Tutte le variabili vengono passate per valore per impostazione predefinita in C #. Stai passando il valore del riferimento nel caso di tipi di riferimento.
Andrew Barber,

Risposte:


503

Gli oggetti non vengono passati affatto. Per impostazione predefinita, l'argomento viene valutato e il suo valore viene passato, per valore, come valore iniziale del parametro del metodo che si sta chiamando. Ora il punto importante è che il valore è un riferimento per i tipi di riferimento - un modo per arrivare a un oggetto (o null). Le modifiche a quell'oggetto saranno visibili dal chiamante. Tuttavia, la modifica del valore del parametro per fare riferimento a un oggetto diverso non sarà visibile quando si utilizza il passaggio per valore, che è l'impostazione predefinita per tutti i tipi.

Se si desidera utilizzare il riferimento pass-by, è necessario utilizzare outo ref, se il tipo di parametro è un tipo di valore o un tipo di riferimento. In tal caso, effettivamente la variabile stessa viene passata per riferimento, quindi il parametro utilizza la stessa posizione di archiviazione dell'argomento e le modifiche al parametro stesso vengono visualizzate dal chiamante.

Così:

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

Ho un articolo che approfondisce molto di più in questo . Fondamentalmente, "passa per riferimento" non significa cosa pensi che significhi.


2
Giusto, non l'ho visto! Sto caricando image = Image.FromFile (..) e quello stava sostituendo l'immagine variabile e non cambiando l'oggetto! :) ovviamente.
michael

1
@Adeem: Non del tutto: non esiste un "oggetto parametro", c'è l'oggetto a cui si riferisce il valore del parametro. Penso che tu abbia avuto l'idea giusta, ma la terminologia conta :)
Jon Skeet,

2
Se eliminiamo le parole chiave refe outda c #, va bene dire che c # passa i parametri allo stesso modo di java, ovvero sempre in base al valore. C'è qualche differenza con Java.
banda larga

1
@broadband: Sì, la modalità di passaggio predefinita è per valore. Anche se ovviamente C # ha puntatori e tipi di valore personalizzati, il che rende tutto un po 'più complicato rispetto a Java.
Jon Skeet,

3
@Vippy: No, per niente. È una copia del riferimento . Ti suggerisco di leggere l'articolo collegato.
Jon Skeet,

18

Un altro esempio di codice per mostrare questo:

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 l'output:

TestPlain: 0

TestRef: 5

TestObjPlain: Test

TestObjRef: TestObjRef


2
Quindi, fondamentalmente, il tipo di riferimento DEVE ESSERE PASSATO come riferimento se vogliamo vedere i cambiamenti nella funzione Chiamante.
Infrangibile il

1
Le stringhe sono tipi di riferimento immutabili. Immutabile significa che non può essere cambiato dopo che è stato creato. Ogni modifica a una stringa creerà una nuova stringa. Ecco perché le stringhe dovevano essere passate come 'ref' per ottenere il cambiamento nel metodo di chiamata. Altri oggetti (ad es. Impiegato) possono essere passati senza "ref" per ripristinare le modifiche nel metodo di chiamata.
Himalaya Garg

1
@vmg, come per HimalayaGarg, questo non è un ottimo esempio. Devi includere un altro esempio di tipo di riferimento che non sia immutabile.
Daniel

11

Molte buone risposte sono state aggiunte. Voglio ancora contribuire, potrebbe essere chiarire leggermente di più.

Quando si passa un'istanza come argomento al metodo, passa copyl'istanza. Ora, se l'istanza che passi è una value type(risiede nel stack) passi la copia di quel valore, quindi se la modifichi, non verrà riflessa nel chiamante. Se l'istanza è un tipo di riferimento, si passa la copia del riferimento (risiede di nuovo in stack) all'oggetto. Quindi hai ottenuto due riferimenti allo stesso oggetto. Entrambi possono modificare l'oggetto. Ma se all'interno del corpo del metodo, si crea un'istanza di nuovo oggetto, la copia del riferimento non farà più riferimento all'oggetto originale, ma farà riferimento al nuovo oggetto appena creato. Quindi finirai per avere 2 riferimenti e 2 oggetti.


Questa dovrebbe essere la risposta scelta!
GEN

Sono completamente d'accordo! :)
JOSE Fu il

8

Immagino sia più chiaro quando lo fai in questo modo. Consiglio di scaricare LinqPad per testare cose come questa.

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 questo dovrebbe produrre

WontUpdate

Nome: Egli, Cognome: Becerra

UpdateImplicitly

Nome: Favio, Cognome: Becerra

UpdateExplicitly

Nome: Favio, Cognome: Becerra


e che dire del vuoto statico pubblico WhatAbout (Person p) {p = new Person () {FirstName = "First", LastName = "Last"}; }. :)
Marin Popov,

4

Quando si passa l' System.Drawing.Imageoggetto type a un metodo, si passa effettivamente una copia di riferimento a quell'oggetto.

Quindi se all'interno di quel metodo stai caricando una nuova immagine, stai caricando usando il riferimento nuovo / copiato. Non stai apportando modifiche all'originale.

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
}


-1

Passa per riferimento Aggiungete solo "ref" nei parametri della funzione e un'altra cosa dovreste dichiarare la funzione "statica" perché main è statica (# 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);  
        }
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.