Nesneleri C # 'da Referans veya Değerle Geçme


234

C # 'da her zaman ilkel olmayan değişkenlerin referansla geçtiğini ve ilkel değerlerin değerle geçtiğini düşündüm.

Dolayısıyla, herhangi bir ilkel olmayan nesneye bir yönteme geçerken, yöntemdeki nesneye yapılan her şey iletilen nesneyi etkiler. (C # 101 şey)

Ancak, bir System.Drawing.Image nesnesini geçirdiğimde, bunun böyle görünmediğini fark ettim? Bir system.drawing.image nesnesini başka bir yönteme geçirir ve bu nesneye bir görüntü yüklersem, bu yöntemin kapsam dışına çıkmasına ve çağıran yönteme geri dönmesine izin verirseniz, bu görüntü orijinal nesneye yüklenmez mi?

Bu neden?


20
Tüm değişkenler C # 'de varsayılan olarak değere geçirilir. Sen geçiyoruz referans değerini referans tiplerinin durumunda.
Andrew Barber

Yanıtlar:


503

Nesneler hiç geçilmez. Varsayılan olarak, bağımsız değişken değerlendirilir ve değeri, aradığınız yöntemin parametresinin başlangıç ​​değeri olarak değere göre iletilir. Şimdi önemli olan nokta, değerin referans türleri için bir referans olmasıdır - bir nesneye (veya null) ulaşmanın bir yolu. Bu nesnede yapılan değişiklikler arayandan görülebilir. Ancak, farklı bir nesneye başvurmak için parametrenin değerini değiştirerek olacaktır değil sizin için varsayılan değer tarafından geçiş kullanırken görünür tüm tipleri.

Tek tek başvuru kullanmak istiyorsanız ,out veya refparametre türünün bir değer türü veya bir başvuru türü olup olmadığını kullanmanız gerekir . Bu durumda, değişkenin kendisi referans olarak geçirilir, bu nedenle parametre argüman ile aynı depolama konumunu kullanır - ve parametredeki değişiklikler arayan tarafından görülür.

Yani:

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

Bu konuda çok daha ayrıntılı bir makalem var . Temel olarak, "referansla geç" ne demek istediğinizi ifade etmez.


2
Hakkın, ben görmedim! Ben image = Image.FromFile (..) yükleniyor ve bu değişken görüntünün yerini alıyor ve nesneyi değiştirmiyor! :) elbette.
michael

1
@Adeem: Pek değil - "parametre nesnesi" yok, parametrenin değerinin başvurduğu nesne var. Sanırım doğru fikre sahipsiniz, ama terminoloji önemli :)
Jon Skeet

2
Anahtar kelimeleri düşerse refve outc # dan, Tamam java değeriyle yani hep yaptığı gibi o c # parametreleri aynı şekilde geçer demek ki. Java ile herhangi bir fark var mı?
geniş bant

1
@broadband: Evet, varsayılan geçiş modu by-value'tır. Tabii ki C #, Java'dan biraz daha karmaşık hale getiren işaretçiler ve özel değer türlerine sahip olmasına rağmen.
Jon Skeet

3
@Vippy: Hayır, hiç değil. Referansın bir kopyası . Bağlantılı makaleyi okumanızı tavsiye ederim.
Jon Skeet

18

Bunu göstermek için bir kod örneği daha:

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

Ve çıktı:

TestPlain: 0

TestRef: 5

TestObjPlain: Test

TestObjRef: TestObjRef


2
Bu nedenle, Arayan işlevindeki değişiklikleri görmek istiyorsak, temel olarak referans türü hala referans olarak GEÇMELİDİR.
kırılmaz

1
Dizeler değişmez referans türleridir. Değişmez anlamına gelir, oluşturulduktan sonra değiştirilemez. Bir dizede yapılan her değişiklik yeni bir dize oluşturur. Bu nedenle, arama yönteminde değişiklik elde etmek için dizelerin 'ref' olarak geçirilmesi gerekiyordu. Diğer nesneler (örn. Çalışan), çağrı yöntemindeki değişiklikleri geri almak için 'ref' olmadan geçirilebilir.
Himalaya Garg

1
@vmg, HimalayaGarg'a göre, bu çok iyi bir örnek değil. Değişmez olmayan başka bir referans türü örneği eklemeniz gerekir.
Daniel

11

Birçok iyi cevap eklenmişti. Hala katkıda bulunmak istiyorum, belki biraz daha açıklığa kavuşacaktır.

Bir örneği yönteme bağımsız değişken olarak ilettiğinizde, copyo örnekten geçer . Şimdi, ilettiğiniz örnek bir value typedeğerse (içinde bulunur stack) , bu değerin kopyasını iletirsiniz , bu nedenle değiştirirseniz, arayana yansıtılmaz. Örnek bir referans türüyse, referansın kopyasını (yine içinde bulunur stack) nesneye iletirsiniz . Yani aynı nesneye iki referansınız var. Her ikisi de nesneyi değiştirebilir. Ancak yöntem gövdesi içinde, başvurunun kopyasının artık orijinal nesneye başvurmayacağı yeni bir nesneyi başlatırsanız, yeni oluşturduğunuz yeni nesneye atıfta bulunacaktır. Böylece 2 referans ve 2 nesneye sahip olacaksınız.


Seçilen cevap bu olmalı!
JAN

Tamamen katılıyorum! :)
JOSEFt

8

Sanırım böyle yaptığınızda daha net. Böyle şeyleri test etmek için LinqPad'i indirmenizi tavsiye ederim .

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

Ve bu çıktı

WontUpdate

Adı: Egli, Soyadı: Becerra

UpdateImplicitly

Adı: Favio, Soyadı: Becerra

UpdateExplicitly

Adı: Favio, Soyadı: Becerra


ve genel statik geçersizliğe ne olacak WhatAbout (Kişi p) {p = new Person () {FirstName = "First", LastName = "Last"}; }. :)
Marin Popov

4

Geçtiğinde System.Drawing.ImageType nesnesini bir yönteme ilettiğinizde, o nesneye bir başvuru kopyası .

Dolayısıyla, bu yöntemin içinde yeni bir resim yüklüyorsanız, yeni / kopyalanan referans kullanarak yüklüyorsunuz. Orijinalde değişiklik yapmıyorsunuz.

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

Referansla Geçiş Yapma İşlev parametrelerine yalnızca "ref" eklersiniz ve main statik (# public void main(String[] args)) olduğu için "statik" işlevini bildirmeniz gerekir .

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);  
        }
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.