C # değere göre geçse vs. referansa göre geç

Consider the following code (I have purposefully written MyPoint to be a reference type for this example)

public class MyPoint
{
    public int x;
    public int y;
}

Referans olarak geçtiğinizde, yöntemin manipüle edilen nesneye bir referans içerdiği evrensel olarak kabul edilir (en azından C# 'da), yönteme göre değeri geçtiğinizde, yöntem manipüle edilen değeri kopyalar, bu nedenle global kapsamdaki değer etkilenmemiş.

Örnek:

void Replace(T a, T b)
{
    a = b;
}

int a = 1;
int b = 2;

Replace(a, b);

// a and b remain unaffected in global scope since a and b are value types.

İşte benim sorunum; MyPoint bir başvuru türüdür, bu nedenle Nokta üzerindeki aynı işlemi genel olarak a yerine b ile bekliyordum kapsamı.

Örnek:

MyPoint a = new MyPoint { x = 1, y = 2 };
MyPoint b = new MyPoint { x = 3, y = 4 };

Replace(a, b);

// a and b remain unaffected in global scope since a and b...ummm!?

Bellekte aynı referansı göstermesi için a ve b kodlarını beklemeliydim ... birileri lütfen nerede yanlış yaptığımı netleştirebilir mi?

5
Örneklerinizden hiçbiri ref veya out kullanmaz, bu nedenle hepsi değere göre geçer (referans).
katma yazar CodesInChaos, kaynak

7 cevap

Re:

Evrensel olarak kabul edilir (en azından C# 'da) referansla geçtiğinizde, yöntemin manipüle edilen nesneye bir referans içerdiği, ancak değere göre geçtiğinde, yöntemin manipüle edilen değeri kopyaladığı ...

TL;DR

ref veya out anahtar kelimelerle değişkenleri iletmezseniz, C# değişkenleri yöntemlere değerine göre iletir.

Ayrıntılı Olarak

Sorun, iki farklı kavramın olmasıdır:

  • Değer Türleri (örneğin, int) ve Referans Türleri (örneğin, dize)
  • Değere Göre Geçiş (varsayılan davranış) vs Referansla Geçiş (ref, out)

Açıkça (herhangi bir) değişkeni referansa göre iletmediğiniz sürece, out veya ref anahtar kelimeleri kullanarak parametreleri, C# 'dan bağımsız olarak Cem'de değer ile geçirin Değişkenin değer tipi mi yoksa referans tipi mi olduğu.

değer türlerini ( int , float veya DateTime gibi yapılar) geçerken çağrılan işlev < a href = "https://stackoverflow.com/a/9251632/314291"> tüm değer türünün bir kopyası (yığın üzerinden).

Değer türünde yapılan herhangi bir değişiklik ve çağrılan işlevden çıkıldığında kopyanın özellik/alanlarındaki herhangi bir değişiklik kaybolacaktır.

Ancak, başvuru türleriyle ( string gibi ve MyPoint sınıfınız gibi özel sınıflar), başvuru 'dır. aynı şekilde, kopyalanıp istif üzerinde iletilen paylaşılan nesne örneği.

Bu şu demek:

  • Any changes to the fields or properties of the shared object are permanent (i.e. any changes to x or y)
  • However, the reference itself is still copied (passed by value), so any change to the copy of the reference will be lost. This is why your code doesn't work as expected

Burada ne oluyor:

void Replace(T a, T b)
{
    a = b;
}

T başvuru türleri için, a nesnesine yapılan yerel değişken (yığın) başvurusunun, b yerel yığın referansına atandığı anlamına gelir. Bu yeniden atama sadece bu fonksiyon için yereldir - kapsam bu fonksiyondan çıkar çıkmaz yeniden atama kaybolur.

Arayanın referanslarını gerçekten değiştirmek istiyorsanız, imzayı şu şekilde değiştirmeniz gerekir:

void Replace(ref T a, T b)
{
    a = b;
}

Bu, çağrıyı referansla ara olarak değiştirir - aslında arayanın değişkeninin adresini işleve aktarıyoruz, bu durumda çağrılan yöntem 'in ' i değiştirmesine izin veriyor arama yönteminin değişkeni.

Ancak, bugünlerde:

Düzenle

Bu iki şema açıklamaya yardımcı olabilir.

Değere göre aktar (referans türleri):

In your first instance (Replace(T a,T b)), a and b are passed by value. For reference types, this means the references are copied onto the stack and passed to the called function.

enter image description here

  1. İlk kodunuz (buna main adını verdim) yönetilen öbek üzerinde iki MyPoint nesnesi tahsis eder (bu point1 adını verdim ve point2 ) ve ardından sırasıyla noktalara referans vermek için a ve b iki yerel değişken referansı atar (açık mavi oklar):
MyPoint a = new MyPoint { x = 1, y = 2 };//point1
MyPoint b = new MyPoint { x = 3, y = 4 };//point2
  1. The call to Replace(a, b) then pushes a copy of the two references onto the stack (the red arrows). Method Replace sees these as the two parameters also named a and b, which still point to point1 and point2, respectively (the orange arrows).

  2. The assignment, a = b; then changes the Replace methods' a local variable such that a now points to the same object as referenced by b (i.e. point2). However, note that this change is only to Replace's local (stack) variables, and this change will only affect subsequent code in Replace (the dark blue line). It does NOT affect the calling function's variable references in any way, NOR does this change the point1 and point2 objects on the heap at all.

Referansa göre ilet:

If however we we change the call to Replace(ref T a, T b) and then change main to pass a by reference, i.e. Replace(ref a, b):

enter image description here

  1. Daha önce olduğu gibi, öbek üzerinde ayrılmış iki nokta nesnesi.

  2. Şimdi, Değiştir (ref a, b) çağrıldığında, main s referansı b ( point2 ) görüşme sırasında hala kopyalanır, a şimdi referansa göre iletilir , yani ana adresin a değişken Replace.

  3. konumuna iletildi
  4. Şimdi a = b ataması yapıldığında ...

  5. Çağıran işlevdir, main 'ın a değişken referansı, şimdi point2 referansı ile güncellenmiştir. a 'a yeniden atama ile yapılan değişiklik şimdi hem main hem de Replace tarafından görülür. Şimdi point1

  6. referansı yok

Nesneye başvuran tüm kod tarafından (ayrılan öbek) nesnesinde yapılan değişiklikler görüldü

Yukarıdaki her iki senaryoda da, point1 ve point2 yığın nesnelerinde aslında hiçbir değişiklik yapılmadı, yalnızca geçirilen ve yeniden atanan yerel değişken başvurularıydı.

Ancak, aslında point1 ve point2 yığın nesnelerinde herhangi bir değişiklik yapılmışsa, bu nesnelere yapılan tüm değişken başvuruları bu değişiklikleri görecektir.

Yani, örneğin:

void main()
{
   MyPoint a = new MyPoint { x = 1, y = 2 };//point1
   MyPoint b = new MyPoint { x = 3, y = 4 };//point2

  //Passed by value, but the properties x and y are being changed
   DoSomething(a, b);

  //a and b have been changed!
   Assert.AreEqual(53, a.x);
   Assert.AreEqual(21, b.y);
}

public void DoSomething(MyPoint a, MyPoint b)
{
   a.x = 53;
   b.y = 21;
}

Şimdi, yürütme main konumuna döndüğünde, main'in değişkenleri ve b , şimdi noktaların x ve y değerlerini okuduklarında değişiklikleri 'görecekler'. Ayrıca a ve b değişkenlerinin hala DoSomething değerine göre iletildiğini unutmayın.

Değer türlerinde yapılan değişiklikler yalnızca yerel kopyayı etkiler

Değer türleri ( System.Int32 , System.Double ve System.DateTime gibi yapılar gibi ilkeler) yığında değil, yığında tahsis edilir. ve bir çağrıya geçtiğinde sözlü olarak yığına kopyalanır. Buradaki bir fark, çağrılan işlev tarafından bir değer türü alanına veya özelliğine yapılan değişikliklerin, yalnızca çağrılan işlev tarafından yerel olarak gözlenmesidir, çünkü yalnızca değer türünün yerel kopyasını mutasyona uğratır.

e.g. Consider the following code with an instance of the mutable struct, System.Drawing.Rectangle

public void SomeFunc(System.Drawing.Rectangle aRectangle)
{
   //Only the local SomeFunc copy of aRectangle is changed:
    aRectangle.X = 99;
   //Passes - the changes last for the scope of the copied variable
    Assert.AreEqual(99, aRectangle.X);
} //The copy aRectangle will be lost when the stack is popped.

// Which when called:
var myRectangle = new System.Drawing.Rectangle(10, 10, 20, 20);
// A copy of `myRectangle` is passed on the stack
SomeFunc(myRectangle);
// Test passes - the caller's struct has NOT been modified
Assert.AreEqual(10, myRectangle.X);

Yukarıdakiler oldukça kafa karıştırıcı olabilir ve neden kendi özel yapılarınızı değişmez olarak yaratmanın iyi bir pratik olduğunu vurgulamaktadır.

ref anahtar sözcüğü, değer türü değişkenlerinin başvuruya göre iletilmesine izin vermek için benzer şekilde çalışır, arayanın değer türü değişkeninin 'adresinin yığına geçirildiğini ve arayanın atanan değişkeninin atanmasının şimdi olduğu doğrudan mümkün.

10
katma

C # aslında değere göre geçer. Onun referansı yanıltıcıdır, çünkü referans türünü geçtiğinizde referansın bir kopyasını alırsınız (referans değere göre iletilir). Bununla birlikte, değiştirme yönteminiz bu referans kopyasını başka bir referansla değiştirdiğinden, etkin bir şekilde hiçbir şey yapmaz (Kopyalanan referans derhal kapsam dışına çıkar). Aslında ref anahtar kelimesini ekleyerek referans olarak geçebilirsiniz:

void Replace(ref T a, T b)
{
    a = b;
}

Bu size istediğiniz sonucu verecektir, fakat pratikte biraz garip.

4
katma

C #, başvuru türlerini referans olarak değil, referansı değil, referansı değere göre iletme 'yi geçiyor. Yani onların içindekilerle uğraşabilirsin, ama ödevi değiştiremezsin.

Daha derin bir anlayış için Jon Skeet tarafından bu harika kitabı okuyun.

2
katma

In C# all the params that you pass to a method are passed by value.
Now before you shout keep on reading:

Bir değer tipinin değeri, bir referans tipinin değeri aslında bir referans iken kopyalanan veridir.

Dolayısıyla, bir metoda referans olarak bir nesneyi ilettiğinizde ve o nesneyi değiştirdiğinizde, değişiklikler aynı hafızayı kullandığınızdan beri nesnenin tahsis edilmesinden bu yana yöntem dışında da yansıyacaktır.

public void Func(Point p){p.x = 4;}
Point p = new Point {x=3,y=4};
Func(p);
// p.x = 4, p.y = 4

Şimdi bu yönteme bakalım:

public void Func2(Point p){
 p = new Point{x=5,y=5};
}
Func2(p);
// p.x = 4, p.y = 4

Yani burada hiçbir değişiklik olmadı ve neden? Metodunuz basitçe yeni bir Nokta yarattı ve p'nin referansını değiştirdi (Değere göre geçti) ve bu nedenle değişiklik yereldi. Noktayı değiştirmediniz, referansı değiştirdiniz ve yerel olarak yaptınız.

And there comes the ref keyword that saves the day:

public void Func3(ref Point p){
 p = new Point{x=5,y=5};
}
Func3(ref p);
// p.x = 5, p.y = 5

Aynısı örneğinizde de oldu. Yeni referansla bir noktaya değindiniz, ancak yerel olarak yaptınız.

1
katma

Varsayılan olarak C#, ALL tartışmalarını değere göre iletir ... bu nedenle a ve b örneklerinde global kapsamda etkilenmez. İşte seçmen adayları için referans.

0
katma
Bence yeni başlayanlar için kafa karışıklığı, referansların bile değere göre iletildiğidir.
katma yazar series0ne, kaynak
@ series0ne. Kabul etti ... bunu kim oyladı?
katma yazar RayLoveless, kaynak

Referansla geçmenin ne demek olduğunu anlamıyorsunuz. Replace yönteminiz Point nesnesinin bir kopyasını oluşturuyor - değere göre (bu aslında bunu yapmanın en iyi yolu)

Referans olarak geçmek için, hem a hem de b'nin bellekteki aynı noktaya referans vermesi için, imzaya "ref" eklemeniz gerekir.

0
katma
Nesnenin kendisi kopyalanmaz, ancak referansıdır. Böylece, o sınıf içindeki herhangi bir şeyi değiştirirseniz, işlevden çıktığınızda değişiklik devam eder.
katma yazar Omri Aharon, kaynak

Doğru anlamıyorsun.

Java'ya benzer - her şey değere göre iletilir! Ama değerin ne olduğunu bilmek zorundasın.

İlkel veri türlerinde, değer sayının kendisidir. Diğer durumlarda referanstır.

AMA, referansı başka bir değişkene kopyalarsanız, aynı referansı tutar, ancak değişkene referans vermez (bu nedenle C ++ 'da bilinen referansa göre geçilmez).

0
katma