Melewati Objek Dengan Referensi atau Nilai dalam C #

234

Dalam C #, saya selalu berpikir bahwa variabel non-primitif diteruskan oleh referensi dan nilai primitif diteruskan oleh nilai.

Jadi ketika melewati suatu metode objek non-primitif, apapun yang dilakukan terhadap objek dalam metode akan mempengaruhi objek yang dilewati. (C # 101 barang)

Namun, saya telah memperhatikan bahwa ketika saya melewati objek System.Drawing.Image, bahwa ini tampaknya tidak terjadi? Jika saya melewatkan objek system.drawing.image ke metode lain, dan memuat gambar ke objek itu, lalu biarkan metode itu keluar dari ruang lingkup dan kembali ke metode pemanggilan, gambar itu tidak dimuat pada objek asli?

Kenapa ini?

michael
sumber
20
Semua variabel dilewatkan oleh nilai secara default di C #. Anda memberikan nilai referensi dalam kasus tipe referensi.
Andrew Barber

Jawaban:

503

Objek tidak lulus sama sekali. Secara default, argumen dievaluasi dan nilainya diteruskan, berdasarkan nilai, sebagai nilai awal dari parameter metode yang Anda panggil. Sekarang poin penting adalah bahwa nilainya adalah referensi untuk tipe referensi - cara untuk sampai ke objek (atau nol). Perubahan pada objek itu akan terlihat dari pemanggil. Namun, mengubah nilai parameter untuk merujuk ke objek yang berbeda tidak akan terlihat ketika Anda menggunakan nilai by pass, yang merupakan default untuk semua jenis.

Jika Anda ingin menggunakan pass-by-reference, Anda harus menggunakan outatau ref, apakah tipe parameter adalah tipe nilai atau tipe referensi. Dalam hal itu, secara efektif variabel itu sendiri dilewatkan oleh referensi, sehingga parameter menggunakan lokasi penyimpanan yang sama dengan argumen - dan perubahan pada parameter itu sendiri dilihat oleh pemanggil.

Begitu:

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

Saya punya artikel yang jauh lebih detail dalam hal ini . Pada dasarnya, "lulus dengan referensi" tidak berarti apa yang Anda pikirkan artinya.

Jon Skeet
sumber
2
Anda benar, saya tidak melihat itu! Saya memuat gambar = Image.FromFile (..) dan itu mengganti gambar variabel dan tidak mengubah objek! :) tentu saja.
michael
1
@ Adeem: Tidak cukup - tidak ada "objek parameter", ada objek yang mengacu pada nilai parameter. Saya pikir Anda punya ide yang tepat, tetapi terminologi penting :)
Jon Skeet
2
Jika kita menjatuhkan kata kunci refdan outdari c #, apakah boleh mengatakan bahwa c # melewati parameter dengan cara yang sama seperti java yaitu selalu dengan nilai. Apakah ada perbedaan dengan java.
broadband
1
@broadband: Ya, mode passing standar adalah menurut nilai. Meskipun tentu saja C # memiliki pointer dan tipe nilai kustom, yang membuatnya lebih rumit daripada di Jawa.
Jon Skeet
3
@ Vippy: Tidak, tidak sama sekali. Ini salinan referensi . Saya sarankan Anda membaca artikel yang ditautkan.
Jon Skeet
18

Satu lagi contoh kode untuk menampilkan ini:

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

Dan hasilnya:

TestPlain: 0

TestRef: 5

TestObjPlain: test

TestObjRef: TestObjRef

vmg
sumber
2
Jadi pada dasarnya tipe referensi masih PERLU LULUS sebagai referensi jika kita ingin melihat perubahan pada fungsi Caller.
Unbreakable
1
String adalah tipe referensi yang tidak berubah. Berarti abadi, itu tidak dapat diubah setelah telah dibuat. Setiap perubahan pada string akan membuat string baru. Itu sebabnya string harus dilewatkan sebagai 'ref' untuk mendapatkan perubahan dalam metode panggilan. Objek lain (misalnya karyawan) dapat dilewati tanpa 'ref' untuk mendapatkan perubahan kembali dalam metode panggilan.
Himalaya Garg
1
@vmg, sesuai HimalayaGarg, ini bukan contoh yang sangat bagus. Anda harus menyertakan contoh jenis referensi lain yang tidak dapat diubah.
Daniel
11

Banyak jawaban bagus telah ditambahkan. Saya masih ingin berkontribusi, mungkin itu akan menjelaskan sedikit lebih banyak.

Ketika Anda melewatkan sebuah instance sebagai argumen ke metode itu melewati copyinstance. Sekarang, jika instance yang Anda lewati adalah value type(berada di dalam stack) Anda meneruskan salinan nilai itu, jadi jika Anda memodifikasinya, itu tidak akan tercermin dalam pemanggil. Jika instance adalah tipe referensi, Anda meneruskan salinan referensi (lagi-lagi berada di stack) ke objek. Jadi Anda punya dua referensi ke objek yang sama. Keduanya dapat memodifikasi objek. Tetapi jika di dalam tubuh metode, Anda instantiate objek baru salinan referensi Anda tidak akan lagi merujuk ke objek asli, itu akan merujuk ke objek baru yang baru saja Anda buat. Jadi Anda akan memiliki 2 referensi dan 2 objek.

OlegI
sumber
Ini harus menjadi jawaban yang dipilih!
JAN
Aku sangat setuju! :)
JOSEFtw
8

Saya kira ini lebih jelas ketika Anda melakukannya seperti ini. Saya sarankan mengunduh LinqPad untuk menguji hal-hal seperti ini.

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

Dan itu seharusnya keluar

Tidak Memperbarui

Nama depan: Egli, Nama belakang: Becerra

Perbarui secara jelas

Nama depan: Favio, Nama belakang: Becerra

Perbarui secara eksplisit

Nama depan: Favio, Nama belakang: Becerra

Egli Becerra
sumber
dan bagaimana dengan public static void WhatAbout (Person p) {p = Person baru () {FirstName = "First", LastName = "Last"}; }. :)
Marin Popov
4

Ketika Anda melewati System.Drawing.Imageobjek tipe ke metode Anda sebenarnya melewati salinan referensi ke objek itu.

Jadi jika di dalam metode itu Anda memuat gambar baru Anda memuat menggunakan referensi baru / disalin. Anda tidak membuat perubahan dalam aslinya.

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
sumber
-1

Dalam Pass By Reference, Anda hanya menambahkan "ref" pada parameter fungsi dan satu hal lagi Anda harus mendeklarasikan fungsi "statis" karena main adalah statis (# 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);  
        }
    }
}
pengguna5593590
sumber